diff --git a/.npmignore b/.npmignore
index f57b4f56d5..2b9bb45213 100644
--- a/.npmignore
+++ b/.npmignore
@@ -7,3 +7,5 @@ scripts/
built/
*build*/
.git/
+bower_components/
+dist/
diff --git a/geo.js b/geo.js
index ff9743ca7c..2585484315 100644
--- a/geo.js
+++ b/geo.js
@@ -5,6 +5,7 @@ window.geo = geo; // jshint ignore: line
geo.renderers = {};
geo.features = {};
geo.fileReaders = {};
+geo.rendererLayerAdjustments = {};
//////////////////////////////////////////////////////////////////////////////
/**
@@ -12,7 +13,7 @@ geo.fileReaders = {};
*/
//////////////////////////////////////////////////////////////////////////////
geo.inherit = function (C, P) { // jshint ignore: line
- "use strict";
+ 'use strict';
var F = inherit.func();
F.prototype = P.prototype;
@@ -20,20 +21,40 @@ geo.inherit = function (C, P) { // jshint ignore: line
C.prototype.constructor = C;
};
geo.inherit.func = function () {
- "use strict";
+ 'use strict';
return function () {};
};
// Should get rid of this at some point.
window.inherit = geo.inherit;
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * This is a helper method for generating new-style subclasses as an
+ * alternative to the older `inherit` classes. Note: these classes
+ * intentionally don't support constructors for the moment. We may
+ * consider alternate semantics such as ES6 classes or stampit
+ * (https://github.com/stampit-org/stampit) as an alternative to handling
+ * private variables.
+ *
+ * @param {object?} props Instance methods and properties to add/override
+ * @returns {object} The inherited object
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.extend = function (props) {
+ 'use strict';
+ var child = Object.create(this.prototype);
+ $.extend(child.prototype, props || {});
+ return child;
+};
+
//////////////////////////////////////////////////////////////////////////////
/**
* Register a new file reader type
*/
//////////////////////////////////////////////////////////////////////////////
geo.registerFileReader = function (name, func) {
- "use strict";
+ 'use strict';
if (geo.fileReaders === undefined) {
geo.fileReaders = {};
@@ -48,7 +69,7 @@ geo.registerFileReader = function (name, func) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.createFileReader = function (name, opts) {
- "use strict";
+ 'use strict';
if (geo.fileReaders.hasOwnProperty(name)) {
return geo.fileReaders[name](opts);
@@ -62,7 +83,7 @@ geo.createFileReader = function (name, opts) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.registerRenderer = function (name, func) {
- "use strict";
+ 'use strict';
if (geo.renderers === undefined) {
geo.renderers = {};
@@ -77,7 +98,7 @@ geo.registerRenderer = function (name, func) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.createRenderer = function (name, layer, canvas, options) {
- "use strict";
+ 'use strict';
if (geo.renderers.hasOwnProperty(name)) {
var ren = geo.renderers[name](
@@ -95,7 +116,7 @@ geo.createRenderer = function (name, layer, canvas, options) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.registerFeature = function (category, name, func) {
- "use strict";
+ 'use strict';
if (geo.features === undefined) {
geo.features = {};
@@ -115,26 +136,71 @@ geo.registerFeature = function (category, name, func) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.createFeature = function (name, layer, renderer, arg) {
- "use strict";
+ 'use strict';
var category = renderer.api(),
- options = {"layer": layer, "renderer": renderer};
+ options = {'layer': layer, 'renderer': renderer};
if (category in geo.features && name in geo.features[category]) {
if (arg !== undefined) {
$.extend(true, options, arg);
}
- return geo.features[category][name](options);
+ var feature = geo.features[category][name](options);
+ layer.gcs = function () {
+ return layer.map().gcs();
+ };
+ return feature;
}
return null;
};
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Register a layer adjustment.
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.registerLayerAdjustment = function (category, name, func) {
+ 'use strict';
+
+ if (geo.rendererLayerAdjustments === undefined) {
+ geo.rendererLayerAdjustments = {};
+ }
+
+ if (!(category in geo.rendererLayerAdjustments)) {
+ geo.rendererLayerAdjustments[category] = {};
+ }
+
+ // TODO Add warning if the name already exists
+ geo.rendererLayerAdjustments[category][name] = func;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * If a layer needs to be adjusted based on the renderer, call the function
+ * that adjusts it.
+ *
+ * @param {string} name Name of the layer.
+ * @param {object} layer Instantiated layer object.
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.adjustLayerForRenderer = function (name, layer) {
+ 'use strict';
+ var rendererName = layer.rendererName();
+ if (rendererName) {
+ if (geo.rendererLayerAdjustments &&
+ geo.rendererLayerAdjustments[rendererName] &&
+ geo.rendererLayerAdjustments[rendererName][name]) {
+ geo.rendererLayerAdjustments[rendererName][name].apply(layer);
+ }
+ }
+};
+
//////////////////////////////////////////////////////////////////////////////
/**
* Register a new layer type
*/
//////////////////////////////////////////////////////////////////////////////
geo.registerLayer = function (name, func) {
- "use strict";
+ 'use strict';
if (geo.layers === undefined) {
geo.layers = {};
@@ -149,10 +215,10 @@ geo.registerLayer = function (name, func) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.createLayer = function (name, map, arg) {
- "use strict";
+ 'use strict';
/// Default renderer is vgl
- var options = {"map": map, "renderer": "vgl"},
+ var options = {'map': map, 'renderer': 'vgl'},
layer = null;
if (name in geo.layers) {
@@ -173,7 +239,7 @@ geo.createLayer = function (name, map, arg) {
*/
//////////////////////////////////////////////////////////////////////////////
geo.registerWidget = function (category, name, func) {
- "use strict";
+ 'use strict';
if (geo.widgets === undefined) {
geo.widgets = {};
@@ -192,24 +258,28 @@ geo.registerWidget = function (category, name, func) {
* Create new instance of the widget
*/
//////////////////////////////////////////////////////////////////////////////
-geo.createWidget = function (name, layer, renderer, arg) {
- "use strict";
+geo.createWidget = function (name, layer, arg) {
+ 'use strict';
- var category = renderer.api(),
- options = {"layer": layer, "renderer": renderer};
- if (category in geo.widgets && name in geo.widgets[category]) {
+ var options = {
+ layer: layer
+ };
+
+ if (name in geo.widgets.dom) {
if (arg !== undefined) {
$.extend(true, options, arg);
}
- return geo.widgets[category][name](options);
+
+ return geo.widgets.dom[name](options);
}
- return null;
+
+ throw new Error('Cannot create unknown widget ' + name);
};
// Add a polyfill for window.requestAnimationFrame.
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (func) {
- "use strict";
+ 'use strict';
window.setTimeout(func, 15);
};
@@ -218,15 +288,22 @@ if (!window.requestAnimationFrame) {
// Add a polyfill for Math.log2
if (!Math.log2) {
Math.log2 = function () {
- "use strict";
+ 'use strict';
return Math.log.apply(Math, arguments) / Math.LN2;
};
}
+// Add a polyfill for Math.sinh
+Math.sinh = Math.sinh || function (x) {
+ 'use strict';
+ var y = Math.exp(x);
+ return (y - 1 / y) / 2;
+};
+
/*global geo*/
-geo.version = "0.5.0";
+geo.version = "0.6.0-rc.1";
//////////////////////////////////////////////////////////////////////////////
/**
@@ -6293,6 +6370,19 @@ vgl.camera = function (arg) {
return this.computeViewMatrix();
};
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Set the view-matrix for the camera and mark that it is up to date so that
+ * it won't be recomputed unless something else changes.
+ *
+ * @param {mat4} view: new view matrix.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.setViewMatrix = function (view) {
+ mat4.copy(m_viewMatrix, view);
+ m_computeModelViewMatrixTime.modified();
+ };
+
////////////////////////////////////////////////////////////////////////////
/**
* Return camera projection matrix This method does not compute the
@@ -6306,6 +6396,19 @@ vgl.camera = function (arg) {
return this.computeProjectionMatrix();
};
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Set the projection-matrix for the camera and mark that it is up to date so
+ * that it won't be recomputed unless something else changes.
+ *
+ * @param {mat4} proj: new projection matrix.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.setProjectionMatrix = function (proj) {
+ mat4.copy(m_projectionMatrix, proj);
+ m_computeProjectMatrixTime.modified();
+ };
+
////////////////////////////////////////////////////////////////////////////
/**
* Return clear mask used by this camera
@@ -6554,7 +6657,7 @@ vgl.camera = function (arg) {
parseFloat(m_parallelExtents.zoom.toFixed(0))) {
return null;
}
- var align = {round: unitsPerPixel, dx: 0, dy: 0};
+ var align = {roundx: unitsPerPixel, roundy: unitsPerPixel, dx: 0, dy: 0};
/* If the screen is an odd number of pixels, shift the view center to the
* center of a pixel so that the pixels fit discretely across the screen.
* If an even number of pixels, align the view center between pixels for
@@ -9122,8 +9225,8 @@ vgl.modelViewOriginUniform = function (name, origin) {
* units-per-pixel, and align.dx and .dy are either 0 or half the size of
* a unit-per-pixel. The alignment guarantees that the texels are
* aligned with screen pixels. */
- view[12] = Math.round(view[12] / align.round) * align.round + align.dx;
- view[13] = Math.round(view[13] / align.round) * align.round + align.dy;
+ view[12] = Math.round(view[12] / align.roundx) * align.roundx + align.dx;
+ view[13] = Math.round(view[13] / align.roundy) * align.roundy + align.dy;
}
this.set(view);
};
@@ -12788,6 +12891,62 @@ vgl.DataBuffers = function (initialSize) {
0
)
};
+ },
+
+ /**
+ * Radius of the earth in meters, from the equatorial radius of SRID 4326.
+ */
+ radiusEarth: 6378137,
+
+ /**
+ * Linearly combine two "coordinate-like" objects in a uniform way.
+ * Coordinate like objects have ``x``, ``y``, and optionally a ``z``
+ * key. The first object is mutated.
+ *
+ * a <= ca * a + cb * b
+ *
+ * @param {number} ca
+ * @param {object} a
+ * @param {number} [a.x=0]
+ * @param {number} [a.y=0]
+ * @param {number} [a.z=0]
+ * @param {number} cb
+ * @param {object} b
+ * @param {number} [b.x=0]
+ * @param {number} [b.y=0]
+ * @param {number} [b.z=0]
+ * @returns {object} ca * a + cb * b
+ */
+ lincomb: function (ca, a, cb, b) {
+ a.x = ca * (a.x || 0) + cb * (b.x || 0);
+ a.y = ca * (a.y || 0) + cb * (b.y || 0);
+ a.z = ca * (a.x || 0) + cb * (b.x || 0);
+ return a;
+ },
+
+ /**
+ * Element-wise product of two coordinate-like object. Mutates
+ * the first object. Note the default values for ``b``, which
+ * are intended to used as a anisotropic scaling factors.
+ *
+ * a <= a * b^pow
+ *
+ * @param {object} a
+ * @param {number} [a.x=0]
+ * @param {number} [a.y=0]
+ * @param {number} [a.z=0]
+ * @param {object} b
+ * @param {number} [b.x=1]
+ * @param {number} [b.y=1]
+ * @param {number} [b.z=1]
+ * @param {number} [pow=1]
+ * @returns {object} a * b^pow
+ */
+ scale: function (a, b, pow) {
+ a.x = (a.x || 0) * Math.pow(b.x || 1, pow);
+ a.y = (a.y || 0) * Math.pow(b.y || 1, pow);
+ a.z = (a.z || 0) * Math.pow(b.z || 1, pow);
+ return a;
}
};
@@ -13817,6 +13976,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
geo.util.ClusterGroup = C;
})();
+(function () {
+ 'use strict';
+
+ geo.util.scale = {
+ d3: d3.scale
+ };
+})();
+
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of class object
@@ -13835,18 +14002,19 @@ geo.object = function () {
var m_this = this,
m_eventHandlers = {},
m_idleHandlers = [],
- m_deferredCount = 0;
+ m_promiseCount = 0;
//////////////////////////////////////////////////////////////////////////////
/**
- * Bind a handler that will be called once when all deferreds are resolved.
+ * Bind a handler that will be called once when all internal promises are
+ * resolved.
*
* @param {function} handler A function taking no arguments
* @returns {geo.object[]|geo.object} this
*/
//////////////////////////////////////////////////////////////////////////////
this.onIdle = function (handler) {
- if (m_deferredCount) {
+ if (m_promiseCount) {
m_idleHandlers.push(handler);
} else {
handler();
@@ -13856,22 +14024,25 @@ geo.object = function () {
//////////////////////////////////////////////////////////////////////////////
/**
- * Add a new deferred object preventing idle event handlers from being called.
+ * Add a new promise object preventing idle event handlers from being called
+ * until it is resolved.
*
- * @param {$.defer} defer A jquery defered object
+ * @param {Promise} promise A promise object
*/
//////////////////////////////////////////////////////////////////////////////
- this.addDeferred = function (defer) {
- m_deferredCount += 1;
- defer.done(function () {
- m_deferredCount -= 1;
- if (!m_deferredCount) {
+ this.addPromise = function (promise) {
+ // called on any resolution of the promise
+ function onDone() {
+ m_promiseCount -= 1;
+ if (!m_promiseCount) {
m_idleHandlers.splice(0, m_idleHandlers.length)
.forEach(function (handler) {
handler();
});
}
- });
+ }
+ m_promiseCount += 1;
+ promise.then(onDone, onDone);
return m_this;
};
@@ -13905,7 +14076,7 @@ geo.object = function () {
/**
* Trigger an event (or events) on this object and call all handlers
*
- * @param {String} event An event from {geo.events}
+ * @param {String} event An event from {geo.event}
* @param {Object} args An optional argument to pass to handlers
*/
//////////////////////////////////////////////////////////////////////////////
@@ -13919,6 +14090,10 @@ geo.object = function () {
return m_this;
}
+ // append the event type to the argument object
+ args = args || {};
+ args.event = event;
+
if (m_eventHandlers.hasOwnProperty(event)) {
m_eventHandlers[event].forEach(function (handler) {
handler.call(m_this, args);
@@ -13942,7 +14117,7 @@ geo.object = function () {
if (event === undefined) {
m_eventHandlers = {};
m_idleHandlers = [];
- m_deferredCount = 0;
+ m_promiseCount = 0;
}
if (Array.isArray(event)) {
event.forEach(function (e) {
@@ -14007,19 +14182,19 @@ geo.sceneObject = function (arg) {
m_children = [],
s_exit = this._exit,
s_trigger = this.geoTrigger,
- s_addDeferred = this.addDeferred,
+ s_addPromise = this.addPromise,
s_onIdle = this.onIdle;
//////////////////////////////////////////////////////////////////////////////
/**
- * Override object.addDeferred to propagate up the scene tree.
+ * Override object.addPromise to propagate up the scene tree.
*/
//////////////////////////////////////////////////////////////////////////////
- this.addDeferred = function (defer) {
+ this.addPromise = function (promise) {
if (m_parent) {
- m_parent.addDeferred(defer);
+ m_parent.addPromise(promise);
} else {
- s_addDeferred(defer);
+ s_addPromise(promise);
}
};
@@ -14183,705 +14358,1490 @@ inherit(geo.timestamp, vgl.timestamp);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create an instance of quadratic surface generator
- * in Cartesian coordinates by the equation
- * (x / a)^2 + (y / b)^2 + (z / c)^2 = 1
. Used
- * primarily to create planetary bodies
+ * This purpose of this class is to provide a generic interface for computing
+ * coordinate transformationss. The interface is taken from the proj4js,
+ * which also provides the geospatial projection implementation. The
+ * interface is intentionally simple to allow for custom, non-geospatial use
+ * cases. For further details, see http://proj4js.org/
*
- * @class
- * @param {Number} [x=0] Radius in X direction
- * @param {Number} [y=0] Radius in Y direction
- * @param {Number} [z=0] Radius in Z direction
+ * The default transforms lat/long coordinates into web mercator
+ * for use with standard tile sets.
*
- * @returns {geo.ellipsoid}
+ * This class is intended to be extended in the future to support 2.5 and 3
+ * dimensional transformations. The forward/inverse methods take optional
+ * z values that are ignored in current mapping context, but will in the
+ * future perform more general 3D transformations.
+ *
+ * @class
+ * @extends geo.object
+ * @param {object} options Constructor options
+ * @param {string} options.source A proj4 string for the source projection
+ * @param {string} options.target A proj4 string for the target projection
+ * @returns {geo.transform}
*/
- //////////////////////////////////////////////////////////////////////////////
-geo.ellipsoid = function (x, y, z) {
- 'use strict';
- if (!(this instanceof geo.ellipsoid)) {
- return new geo.ellipsoid(x, y, z);
- }
-
- x = vgl.defaultValue(x, 0.0);
- y = vgl.defaultValue(y, 0.0);
- z = vgl.defaultValue(z, 0.0);
+//////////////////////////////////////////////////////////////////////////////
- if (x < 0.0 || y < 0.0 || z < 0.0) {
- return console.log('[error] Al radii components must be greater than zero');
+geo.transform = function (options) {
+ 'use strict';
+ if (!(this instanceof geo.transform)) {
+ return new geo.transform(options);
}
var m_this = this,
- m_radii = new vec3.fromValues(x, y, z),
- m_radiiSquared = new vec3.fromValues(
- x * x, y * y, z * z),
- m_minimumRadius = Math.min(x, y, z),
- m_maximumRadius = Math.max(x, y, z);
+ m_proj, // The raw proj4js object
+ m_source, // The source projection
+ m_target; // The target projection
- ////////////////////////////////////////////////////////////////////////////
/**
- * Return radii of ellipsoid
+ * Generate the internal proj4 object.
+ * @private
*/
- ////////////////////////////////////////////////////////////////////////////
- this.radii = function () {
- return m_radii;
- };
+ function generate_proj4() {
+ m_proj = new proj4(
+ m_this.source(),
+ m_this.target()
+ );
+ }
- ////////////////////////////////////////////////////////////////////////////
/**
- * Return squared radii of the ellipsoid
+ * Get/Set the source projection
*/
- ////////////////////////////////////////////////////////////////////////////
- this.radiiSquared = function () {
- return m_radiiSquared;
+ this.source = function (arg) {
+ if (arg === undefined) {
+ return m_source || 'EPSG:4326';
+ }
+ m_source = arg;
+ generate_proj4();
+ return m_this;
};
- ////////////////////////////////////////////////////////////////////////////
/**
- * Return maximum radius of the ellipsoid
- *
- * @return {Number} The maximum radius of the ellipsoid
+ * Get/Set the target projection
*/
- ////////////////////////////////////////////////////////////////////////////
- this.maximumRadius = function () {
- return m_maximumRadius;
+ this.target = function (arg) {
+ if (arg === undefined) {
+ return m_target || 'EPSG:3857';
+ }
+ m_target = arg;
+ generate_proj4();
+ return m_this;
};
- ////////////////////////////////////////////////////////////////////////////
/**
- * Return minimum radius of the ellipsoid
+ * Perform a forward transformation (source -> target)
+ * @protected
+ *
+ * @param {object} point The point coordinates
+ * @param {number} point.x The x-coordinate (i.e. longitude)
+ * @param {number} point.y The y-coordinate (i.e. latitude)
+ * @param {number} [point.z=0] The z-coordinate (i.e. elevation)
*
- * @return {Number} The maximum radius of the ellipsoid
+ * @returns {object} A point object in the target coordinates
*/
- ////////////////////////////////////////////////////////////////////////////
- this.minimumRadius = function () {
- return m_minimumRadius;
+ this._forward = function (point) {
+ var pt = m_proj.forward(point);
+ pt.z = point.z || 0;
+ return pt;
};
- ////////////////////////////////////////////////////////////////////////////
/**
- * Computes the normal of the plane tangent to the surface of
- * the ellipsoid at the provided position
- *
- * @param {Number} lat The cartographic latitude for which
- * to to determine the geodetic normal
- * @param {Number} lon The cartographic longitude for which
- * to to determine the geodetic normal
+ * Perform an inverse transformation (target -> source)
+ * @protected
*
- * @return {vec3}
+ * @param {object} point The point coordinates
+ * @param {number} point.x The x-coordinate (i.e. longitude)
+ * @param {number} point.y The y-coordinate (i.e. latitude)
+ * @param {number} [point.z=0] The z-coordinate (i.e. elevation)
*
- * @exception {DeveloperError} cartographic is required.
+ * @returns {object} A point object in the source coordinates
*/
- ////////////////////////////////////////////////////////////////////////////
- this.computeGeodeticSurfaceNormal = function (lat, lon) {
- if (typeof lat === 'undefined' || typeof lon === 'undefined') {
- throw '[error] Valid latitude and longitude is required';
- }
-
- var cosLatitude = Math.cos(lat),
- result = vec3.create();
-
- result[0] = cosLatitude * Math.cos(lon);
- result[1] = cosLatitude * Math.sin(lon);
- result[2] = Math.sin(lat);
-
- vec3.normalize(result, result);
- return result;
+ this._inverse = function (point) {
+ var pt = m_proj.inverse(point);
+ pt.z = point.z || 0;
+ return pt;
};
- ////////////////////////////////////////////////////////////////////////////
/**
- * Converts the provided geographic latitude, longitude,
- * and height to WGS84 coordinate system
+ * Perform a forward transformation (source -> target) in place
+ *
+ * @param {object[]} point The point coordinates or array of points
+ * @param {number} point.x The x-coordinate (i.e. longitude)
+ * @param {number} point.y The y-coordinate (i.e. latitude)
+ * @param {number} [point.z=0] The z-coordinate (i.e. elevation)
*
- * @param {Number} lat Latitude in radians
- * @param {Number} lon Longitude in radians
- * @param {Number} elev Elevation
- * @return {vec3} Position in the WGS84 coordinate system
+ * @returns {object} A point object or array in the target coordinates
*/
- ////////////////////////////////////////////////////////////////////////////
- this.transformPoint = function (lat, lon, elev) {
- lat = lat * (Math.PI / 180.0);
- lon = lon * (Math.PI / 180.0);
-
- var n = m_this.computeGeodeticSurfaceNormal(lat, lon),
- k = vec3.create(),
- gamma = Math.sqrt(vec3.dot(n, k)),
- result = vec3.create();
-
- vec3.multiply(k, m_radiiSquared, n);
- vec3.scale(k, k, 1 / gamma);
- vec3.scale(n, n, elev);
- vec3.add(result, n, k);
- return result;
+ this.forward = function (point) {
+ if (Array.isArray(point)) {
+ return point.map(m_this._forward);
+ }
+ return m_this._forward(point);
};
- ////////////////////////////////////////////////////////////////////////////
/**
- * Converts the provided geographic latitude, longitude,
- * and height to WGS84 coordinate system
+ * Perform an inverse transformation (target -> source) in place
+ * @protected
+ *
+ * @param {object[]} point The point coordinates or array of points
+ * @param {number} point.x The x-coordinate (i.e. longitude)
+ * @param {number} point.y The y-coordinate (i.e. latitude)
+ * @param {number} [point.z=0] The z-coordinate (i.e. elevation)
*
- * @param {vgl.geometryData} geom
+ * @returns {object} A point object in the source coordinates
*/
- ////////////////////////////////////////////////////////////////////////////
- this.transformGeometry = function (geom) {
- if (!geom) {
- throw '[error] Failed to transform to cartesian. Invalid geometry.';
- }
-
- var sourceData = geom.sourceData(vgl.vertexAttributeKeys.Position),
- sourceDataArray = sourceData.data(),
- noOfComponents = sourceData.attributeNumberOfComponents(
- vgl.vertexAttributeKeys.Position),
- stride = sourceData.attributeStride(
- vgl.vertexAttributeKeys.Position),
- offset = sourceData.attributeOffset(
- vgl.vertexAttributeKeys.Position),
- sizeOfDataType = sourceData.sizeOfAttributeDataType(
- vgl.vertexAttributeKeys.Position),
- index = null,
- count = sourceDataArray.length * (1.0 / noOfComponents),
- gamma = null,
- n = null,
- j = 0,
- k = vec3.create(),
- result = vec3.create();
-
- stride /= sizeOfDataType;
- offset /= sizeOfDataType;
-
- if (noOfComponents !== 3) {
- throw ('[error] Requires positions with three components');
- }
-
- for (j = 0; j < count; j += 1) {
- index = j * stride + offset;
-
- sourceDataArray[index] = sourceDataArray[index] * (Math.PI / 180.0);
- sourceDataArray[index + 1] = sourceDataArray[index + 1] * (Math.PI / 180.0);
-
- n = m_this.computeGeodeticSurfaceNormal(sourceDataArray[index + 1],
- sourceDataArray[index]);
- vec3.multiply(k, m_radiiSquared, n);
- gamma = Math.sqrt(vec3.dot(n, k));
- vec3.scale(k, k, 1 / gamma);
- vec3.scale(n, n, sourceDataArray[index + 2]);
- vec3.add(result, n, k);
-
- sourceDataArray[index] = result[0];
- sourceDataArray[index + 1] = result[1];
- sourceDataArray[index + 2] = result[2];
+ this.inverse = function (point) {
+ if (Array.isArray(point)) {
+ return point.map(m_this._inverse);
}
+ return m_this._inverse(point);
};
- return m_this;
-};
-
-////////////////////////////////////////////////////////////////////////////
-/**
- * An Ellipsoid instance initialized to the WGS84 standard.
- * @memberof ellipsoid
- *
- */
-////////////////////////////////////////////////////////////////////////////
-geo.ellipsoid.WGS84 = vgl.freezeObject(
- geo.ellipsoid(6378137.0, 6378137.0, 6356752.3142451793));
-
-////////////////////////////////////////////////////////////////////////////
-/**
- * An Ellipsoid instance initialized to radii of (1.0, 1.0, 1.0).
- * @memberof ellipsoid
- */
-////////////////////////////////////////////////////////////////////////////
-geo.ellipsoid.UNIT_SPHERE = vgl.freezeObject(
- geo.ellipsoid(1.0, 1.0, 1.0));
+ // Set defaults given by the constructor
+ options = options || {};
+ this.source(options.source);
+ this.target(options.target);
-/** @namespace */
-geo.mercator = {
- r_major: 6378137.0 //Equatorial Radius, WGS84
+ geo.object.call(this);
+ return this;
};
-//////////////////////////////////////////////////////////////////////////////
/**
- * Returns the polar radius based on the projection.
+ * Transform an array of coordinates from one projection into another. The
+ * transformation may occur in place (modifying the input coordinate array),
+ * depending on the input format. The coordinates can be an object with x, y,
+ * and (optionally z) or an array of 2 or 3 values, or an array of either of
+ * those, or a single flat array with 2 or 3 components per coordinate. Arrays
+ * are always modified in place. Individual point objects are not altered; new
+ * point objects are returned unless no transform is needed.
*
- * @param {Boolean} sperical
- * @returns {Number}
+ * @param {string} srcPrj The source projection
+ * @param {string} tgtPrj The destination projection
+ * @param {geoPosition[]} coordinates An array of coordinate objects
+ * @param {number} numberOfComponents for flat arrays, either 2 or 3.
+ *
+ * @returns {geoPosition[]} The transformed coordinates
*/
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.r_minor = function (spherical) {
+geo.transform.transformCoordinates = function (
+ srcPrj, tgtPrj, coordinates, numberOfComponents) {
'use strict';
- var r_minor;
-
- spherical = spherical !== undefined ? spherical : false;
-
- if (spherical) {
- r_minor = 6378137.0;
- } else {
- r_minor = 6356752.314245179;
+ if (srcPrj === tgtPrj) {
+ return coordinates;
}
- return r_minor;
-};
+ var i, count, offset, xAcc, yAcc, zAcc, writer, output, projPoint,
+ trans = geo.transform({source: srcPrj, target: tgtPrj});
-//////////////////////////////////////////////////////////////////////////////
-/**
- * 1/f=(a-b)/a , a=r_major, b=r_minor
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.f = function (spherical) {
- 'use strict';
+ /// Default Z accessor
+ zAcc = function () {
+ return 0.0;
+ };
- return (geo.mercator.r_major - geo.mercator.r_minor(spherical)) / geo.mercator.r_major;
-};
+ /// Helper methods
+ function handleArrayCoordinates() {
+ if (coordinates[0] instanceof Array) {
+ if (coordinates[0].length === 2) {
+ xAcc = function (index) {
+ return coordinates[index][0];
+ };
+ yAcc = function (index) {
+ return coordinates[index][1];
+ };
+ writer = function (index, x, y) {
+ output[index] = [x, y];
+ };
+ } else if (coordinates[0].length === 3) {
+ xAcc = function (index) {
+ return coordinates[index][0];
+ };
+ yAcc = function (index) {
+ return coordinates[index][1];
+ };
+ zAcc = function (index) {
+ return coordinates[index][2];
+ };
+ writer = function (index, x, y, z) {
+ output[index] = [x, y, z];
+ };
+ } else {
+ throw 'Invalid coordinates. Requires two or three components per array';
+ }
+ } else {
+ if (coordinates.length === 2) {
+ offset = 2;
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert longitude (Degree) to Tile X
- *
- * @param {float} lon
- * @param {integer} z
- * @returns {integer}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.long2tilex = function (lon, z) {
- 'use strict';
- var rad = (lon + 180.0) / 360.0,
- f = Math.floor(rad * Math.pow(2.0, z));
- return f;
-};
+ xAcc = function (index) {
+ return coordinates[index * offset];
+ };
+ yAcc = function (index) {
+ return coordinates[index * offset + 1];
+ };
+ writer = function (index, x, y) {
+ output[index] = x;
+ output[index + 1] = y;
+ };
+ } else if (coordinates.length === 3) {
+ offset = 3;
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert latitude (Degree) to Tile Y
- *
- * @param {float} lat
- * @param {integer} z
- * @returns {integer}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.lat2tiley = function (lat, z) {
- 'use strict';
- var rad = lat * Math.PI / 180.0;
- return Math.floor((1.0 - rad / Math.PI) / 2.0 * Math.pow(2.0, z));
-};
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert Longitute (Degree) to Tile X and fraction.
- *
- * @param {float} lon
- * @param {integer} z
- * @returns {number[]}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.long2tilex2 = function (lon, z) {
- 'use strict';
- var rad = (lon + 180.0) / 360.0,
- f = rad * Math.pow(2.0, z),
- ret = Math.floor(f),
- frac = f - ret;
- return [ret, frac];
-};
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert Latitude (Degree) to Tile Y and fraction
- *
- * @param {float} lat
- * @param {integer} z
- * @returns {number[]}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.lat2tiley2 = function (lat, z) {
- 'use strict';
- var rad = lat * Math.PI / 180.0,
- f = (1.0 - Math.log(Math.tan(rad) + 1.0 / Math.cos(rad)) /
- Math.PI) / 2.0 * Math.pow(2.0, z),
- ret = Math.floor(f),
- frac = f - ret;
- return [ret, frac];
-};
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert Tile X to Longitute (Degree)
- *
- * @param {integer} x
- * @param {integer} z
- * @returns {float}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.tilex2long = function (x, z) {
- 'use strict';
- return x / Math.pow(2.0, z) * 360.0 - 180.0;
-};
+ xAcc = function (index) {
+ return coordinates[index * offset];
+ };
+ yAcc = function (index) {
+ return coordinates[index * offset + 1];
+ };
+ zAcc = function (index) {
+ return coordinates[index * offset + 2];
+ };
+ writer = function (index, x, y, z) {
+ output[index] = x;
+ output[index + 1] = y;
+ output[index + 2] = z;
+ };
+ } else if (numberOfComponents) {
+ if (numberOfComponents === 2 || numberOfComponents === 3) {
+ offset = numberOfComponents;
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert Tile Y to Latitute (Degree)
- *
- * @param {integer} y
- * @param {integer} z
- * @returns {float}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.tiley2lat = function (y, z) {
- 'use strict';
- var n = Math.PI - 2.0 * Math.PI * y / Math.pow(2.0, z);
- return 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
-};
+ xAcc = function (index) {
+ return coordinates[index];
+ };
+ yAcc = function (index) {
+ return coordinates[index + 1];
+ };
+ if (numberOfComponents === 2) {
+ writer = function (index, x, y) {
+ output[index] = x;
+ output[index + 1] = y;
+ };
+ } else {
+ zAcc = function (index) {
+ return coordinates[index + 2];
+ };
+ writer = function (index, x, y, z) {
+ output[index] = x;
+ output[index + 1] = y;
+ output[index + 2] = z;
+ };
+ }
+ } else {
+ throw 'Number of components should be two or three';
+ }
+ } else {
+ throw 'Invalid coordinates';
+ }
+ }
+ }
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert spherical mercator Y to latitude
- *
- * @param {float} a
- * @returns {float}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.y2lat = function (a) {
- 'use strict';
- return 180 / Math.PI * (2 * Math.atan(Math.exp(a * Math.PI / 180)) - Math.PI / 2);
-};
+ /// Helper methods
+ function handleObjectCoordinates() {
+ if (coordinates[0] &&
+ 'x' in coordinates[0] &&
+ 'y' in coordinates[0]) {
+ xAcc = function (index) {
+ return coordinates[index].x;
+ };
+ yAcc = function (index) {
+ return coordinates[index].y;
+ };
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert latitude into Y position in spherical mercator
- *
- * @param {float} a
- * @returns {float}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.lat2y = function (a) {
- 'use strict';
- return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + a * (Math.PI / 180) / 2));
-};
+ if ('z' in coordinates[0]) {
+ zAcc = function (index) {
+ return coordinates[index].z;
+ };
+ writer = function (index, x, y, z) {
+ output[i] = {x: x, y: y, z: z};
+ };
+ } else {
+ writer = function (index, x, y) {
+ output[index] = {x: x, y: y};
+ };
+ }
+ } else if (coordinates && 'x' in coordinates && 'y' in coordinates) {
+ xAcc = function () {
+ return coordinates.x;
+ };
+ yAcc = function () {
+ return coordinates.y;
+ };
-//////////////////////////////////////////////////////////////////////////////
-/**
- *
- * @param {float} d
- * @returns {number}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.deg2rad = function (d) {
- 'use strict';
- var r = d * (Math.PI / 180.0);
- return r;
-};
+ if ('z' in coordinates) {
+ zAcc = function () {
+ return coordinates.z;
+ };
+ writer = function (index, x, y, z) {
+ output = {x: x, y: y, z: z};
+ };
+ } else {
+ writer = function (index, x, y) {
+ output = {x: x, y: y};
+ };
+ }
+ } else {
+ throw 'Invalid coordinates';
+ }
+ }
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert radian to degree
- *
- * @param {float} r
- * @returns {number}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.rad2deg = function (r) {
- 'use strict';
- var d = r / (Math.PI / 180.0);
- return d;
-};
+ if (coordinates instanceof Array) {
+ output = [];
+ output.length = coordinates.length;
+ count = coordinates.length;
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Convert latlon to mercator
- *
- * @param {float} lon
- * @param {float} lat
- * @returns {object}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.ll2m = function (lon, lat, spherical) {
- 'use strict';
+ if (coordinates[0] instanceof Array ||
+ coordinates[0] instanceof Object) {
+ offset = 1;
- if (lat > 89.5) {
- lat = 89.5;
+ if (coordinates[0] instanceof Array) {
+ handleArrayCoordinates();
+ } else if (coordinates[0] instanceof Object) {
+ handleObjectCoordinates();
+ }
+ } else {
+ handleArrayCoordinates();
+ }
+ } else if (coordinates && coordinates instanceof Object) {
+ count = 1;
+ offset = 1;
+ if (coordinates && 'x' in coordinates && 'y' in coordinates) {
+ handleObjectCoordinates();
+ } else {
+ throw 'Coordinates are not valid';
+ }
}
- if (lat < -89.5) {
- lat = -89.5;
+ for (i = 0; i < count; i += offset) {
+ projPoint = trans.forward({x: xAcc(i), y: yAcc(i), z: zAcc(i)});
+ writer(i, projPoint.x, projPoint.y, projPoint.z);
}
-
- var x = this.r_major * this.deg2rad(lon),
- temp = this.r_minor(spherical) / this.r_major,
- es = 1.0 - (temp * temp),
- eccent = Math.sqrt(es),
- phi = this.deg2rad(lat),
- sinphi = Math.sin(phi),
- con = eccent * sinphi,
- com = 0.5 * eccent,
- con2 = Math.pow((1.0 - con) / (1.0 + con), com),
- ts = Math.tan(0.5 * (Math.PI * 0.5 - phi)) / con2,
- y = -this.r_major * Math.log(ts),
- ret = {'x': x, 'y': y};
-
- return ret;
+ return output;
};
-//////////////////////////////////////////////////////////////////////////////
/**
- * Convert mercator to lat lon
+ * Apply an affine transformation consisting of a translation
+ * then a scaling to the given coordinate array. Note, the
+ * transformation occurs in place so the input coordinate
+ * object are mutated.
+ *
+ * (Possibly extend to support rotations as well)
*
- * @param {float} x
- * @param {float} y
+ * @param {object} def
+ * @param {object} def.origin The transformed origin
+ * @param {object} def.scale The transformed scale factor
+ * @param {object[]} coords An array of coordinate objects
+ *
+ * @returns {object[]} The transformed coordinates
*/
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.m2ll = function (x, y, spherical) {
+geo.transform.affineForward = function (def, coords) {
'use strict';
- var lon = this.rad2deg((x / this.r_major)),
- temp = this.r_minor(spherical) / this.r_major,
- e = Math.sqrt(1.0 - (temp * temp)),
- lat = this.rad2deg(this.pjPhi2(Math.exp(-(y / this.r_major)), e)),
- ret = {'lon': lon, 'lat': lat};
-
- return ret;
+ var i, origin = def.origin, scale = def.scale || {x: 1, y: 1, z: 1};
+ for (i = 0; i < coords.length; i += 1) {
+ coords[i].x = (coords[i].x - origin.x) * scale.x;
+ coords[i].y = (coords[i].y - origin.y) * scale.y;
+ coords[i].z = ((coords[i].z || 0) - (origin.z || 0)) * scale.z;
+ }
+ return coords;
};
-//////////////////////////////////////////////////////////////////////////////
/**
- * pjPhi2
+ * Apply an inverse affine transformation which is the
+ * inverse to {@link geo.transform.affineForward}. Note, the
+ * transformation occurs in place so the input coordinate
+ * object are mutated.
*
- * @param {float} ts
- * @param {float} e
- * @returns {number}
+ * (Possibly extend to support rotations as well)
+ *
+ * @param {object} def
+ * @param {object} def.origin The transformed origin
+ * @param {object} def.scale The transformed scale factor
+ * @param {object[]} coords An array of coordinate objects
+ *
+ * @returns {object[]} The transformed coordinates
*/
-//////////////////////////////////////////////////////////////////////////////
-geo.mercator.pjPhi2 = function (ts, e) {
+geo.transform.affineInverse = function (def, coords) {
'use strict';
- var N_ITER = 15,
- HALFPI = Math.PI / 2,
- TOL = 0.0000000001,
- con, dphi,
- i = N_ITER,
- eccnth = 0.5 * e,
- Phi = HALFPI - 2.0 * Math.atan(ts);
-
- do {
- con = e * Math.sin(Phi);
- dphi = HALFPI - 2.0 * Math.atan(ts * Math.pow(
- (1.0 - con) / (1.0 + con), eccnth)) - Phi;
- Phi += dphi;
- i -= 1;
- } while (Math.abs(dphi) > TOL && i);
- return Phi;
+ var i, origin = def.origin, scale = def.scale || {x: 1, y: 1, z: 1};
+ for (i = 0; i < coords.length; i += 1) {
+ coords[i].x = coords[i].x / scale.x + origin.x;
+ coords[i].y = coords[i].y / scale.y + origin.y;
+ coords[i].z = (coords[i].z || 0) / scale.z + (origin.z || 0);
+ }
+ return coords;
};
+inherit(geo.transform, geo.object);
-//////////////////////////////////////////////////////////////////////////////
-/**
- * @class
- * @extends geo.sceneObject
- * @param {Object?} arg An options argument
- * @param {string} arg.attribution An attribution string to display
- * @returns {geo.layer}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.layer = function (arg) {
- "use strict";
-
- if (!(this instanceof geo.layer)) {
- return new geo.layer(arg);
- }
- arg = arg || {};
- geo.sceneObject.call(this, arg);
+(function () {
+ 'use strict';
//////////////////////////////////////////////////////////////////////////////
/**
- * @private
- */
- //////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_exit = this._exit,
- m_style = arg.style === undefined ? {"opacity": 0.5,
- "color": [0.8, 0.8, 0.8],
- "visible": true,
- "bin": 100} : arg.style,
- m_id = arg.id === undefined ? geo.layer.newLayerId() : arg.id,
- m_name = "",
- m_gcs = "EPSG:4326",
- m_timeRange = null,
- m_source = arg.source || null,
- m_map = arg.map === undefined ? null : arg.map,
- m_isReference = false,
- m_x = 0,
- m_y = 0,
- m_width = 0,
- m_height = 0,
- m_node = null,
- m_canvas = null,
- m_renderer = null,
- m_initialized = false,
- m_rendererName = arg.renderer === undefined ? "vgl" : arg.renderer,
- m_dataTime = geo.timestamp(),
- m_updateTime = geo.timestamp(),
- m_drawTime = geo.timestamp(),
- m_sticky = arg.sticky === undefined ? true : arg.sticky,
- m_active = arg.active === undefined ? true : arg.active,
- m_attribution = arg.attribution || null;
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get whether or not the layer is sticky (navigates with the map).
+ * This class defines the raw interface for a camera. At a low level, the
+ * camera provides a methods for converting between a map's coordinate system
+ * to display pixel coordinates.
*
- * @returns {Boolean}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.sticky = function () {
- return m_sticky;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get whether or not the layer is active. An active layer will receive
- * native mouse when the layer is on top. Non-active layers will never
- * receive native mouse events.
+ * For the moment, all camera trasforms are assumed to be expressible as
+ * 4x4 matrices. More general cameras may follow that break this assumption.
*
- * @returns {Boolean}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.active = function () {
- return m_active;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set root node of the layer
+ * The interface for the camera is relatively stable for "map-like" views,
+ * i.e. when the camera is pointing in the direction [0, 0, -1], and placed
+ * above the z=0 plane. More general view changes and events have not yet
+ * been defined.
*
- * @returns {div}
+ * The camera emits the following events when the view changes:
+ *
+ * * {@link geo.event.camera.pan} when the camera is translated in the
+ * x/y plane
+ * * {@link geo.event.camera.zoom} when the camera is changed in a way
+ * that modifies the current zoom level
+ * * {@link geo.event.camera.view} when the visible bounds change for
+ * any reason
+ * * {@link geo.event.camera.projection} when the projection type changes
+ * * {@link geo.event.camera.viewport} when the viewport changes
+ *
+ * By convention, protected methods do not update the internal matrix state,
+ * public methods do. For now, there are two primary methods that are
+ * inteded to be used by external classes to mutate the internal state:
+ *
+ * * bounds: Set the visible bounds (for initialization and zooming)
+ * * pan: Translate the camera in x/y by an offset (for panning)
+ *
+ * @class
+ * @extends geo.object
+ * @param {object?} spec Options argument
+ * @param {string} spec.projection One of the supported geo.camera.projection
+ * @param {object} spec.viewport The initial camera viewport
+ * @param {object} spec.viewport.width
+ * @param {object} spec.viewport.height
+ * @returns {geo.camera}
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ geo.camera = function (spec) {
+ if (!(this instanceof geo.camera)) {
+ return new geo.camera(spec);
+ }
+ spec = spec || {};
+ geo.object.call(this, spec);
+
+ /**
+ * The view matrix
+ * @protected
+ */
+ this._view = mat4.create();
+
+ /**
+ * The projection matrix
+ * @protected
+ */
+ this._proj = mat4.create();
+
+ /**
+ * The projection type (one of `this.constructor.projection`)
+ * @protected
+ */
+ this._projection = null;
+
+ /**
+ * The transform matrix (view * proj)
+ * @protected
+ */
+ this._transform = mat4.create();
+
+ /**
+ * The inverse transform matrix (view * proj)^-1
+ * @protected
+ */
+ this._inverse = mat4.create();
+
+ /**
+ * Cached bounds object recomputed on demand.
+ * @protected
+ */
+ this._bounds = null;
+
+ /**
+ * Cached "display" matrix recomputed on demand.
+ * @see {@link geo.camera.display}
+ * @protected
+ */
+ this._display = null;
+
+ /**
+ * Cached "world" matrix recomputed on demand.
+ * @see {@link geo.camera.world}
+ * @protected
+ */
+ this._world = null;
+
+ /**
+ * The viewport parameters size and offset.
+ * @property {number} height Viewport height in pixels
+ * @property {number} width Viewport width in pixels
+ * @protected
+ */
+ this._viewport = {width: 1, height: 1};
+
+ /**
+ * Set up the projection matrix for the current projection type.
+ * @protected
+ */
+ this._createProj = function () {
+ var s = this.constructor.bounds.near / this.constructor.bounds.far;
+
+ // call mat4.frustum or mat4.ortho here
+ if (this._projection === 'perspective') {
+ mat4.frustum(
+ this._proj,
+ this.constructor.bounds.left * s,
+ this.constructor.bounds.right * s,
+ this.constructor.bounds.bottom * s,
+ this.constructor.bounds.top * s,
+ -this.constructor.bounds.near,
+ -this.constructor.bounds.far
+ );
+ } else if (this._projection === 'parallel') {
+ mat4.ortho(
+ this._proj,
+ this.constructor.bounds.left,
+ this.constructor.bounds.right,
+ this.constructor.bounds.bottom,
+ this.constructor.bounds.top,
+ this.constructor.bounds.near,
+ this.constructor.bounds.far
+ );
+ }
+ };
+
+ /**
+ * Update the internal state of the camera on change to camera
+ * parameters.
+ * @protected
+ */
+ this._update = function () {
+ this._bounds = null;
+ this._display = null;
+ this._world = null;
+ this._transform = geo.camera.combine(this._proj, this._view);
+ mat4.invert(this._inverse, this._transform);
+ this.geoTrigger(geo.event.camera.view, {
+ camera: this
+ });
+ };
+
+ /**
+ * Getter/setter for the view matrix.
+ * @note copies the matrix value on set.
+ */
+ Object.defineProperty(this, 'view', {
+ get: function () {
+ return this._view;
+ },
+ set: function (view) {
+ mat4.copy(this._view, view);
+ this._update();
+ }
+ });
+
+ /**
+ * Getter/setter for the view bounds.
+ *
+ * If not provided, near and far bounds will be set to [-1, 1] by
+ * default. We will probably want to change this to a unit specific
+ * value initialized by the map when drawing true 3D objects or
+ * tilting the camera.
+ *
+ * Returned near/far bounds are also -1, 1 for the moment.
+ */
+ Object.defineProperty(this, 'bounds', {
+ get: function () {
+ if (this._bounds === null) {
+ this._bounds = this._getBounds();
+ }
+ return this._bounds;
+ },
+ set: function (bounds) {
+ this._setBounds(bounds);
+ this._update();
+ }
+ });
+
+ /**
+ * Getter for the "display" matrix. This matrix converts from
+ * world coordinates into display coordinates. This matrix exists to
+ * generate matrix3d css transforms that can be used in layers that
+ * render on the DOM.
+ */
+ Object.defineProperty(this, 'display', {
+ get: function () {
+ var mat;
+ if (this._display === null) {
+ mat = geo.camera.affine(
+ {x: 1, y: 1}, // translate to: [0, 2] x [0, 2]
+ {
+ x: this.viewport.width / 2,
+ y: this.viewport.height / -2
+ } // scale to: [0, width] x [-height, 0]
+ );
+
+ // applies mat to the transform (world -> normalized)
+ this._display = geo.camera.combine(
+ mat,
+ this._transform
+ );
+ }
+ return this._display;
+ }
+ });
+
+ /**
+ * Getter for the "world" matrix. This matrix converts from
+ * display coordinates into world coordinates. This is constructed
+ * by inverting the "display" matrix.
+ */
+ Object.defineProperty(this, 'world', {
+ get: function () {
+ if (this._world === null) {
+ this._world = mat4.invert(
+ mat4.create(),
+ this.display
+ );
+ }
+ return this._world;
+ }
+ });
+
+ /**
+ * Getter/setter for the projection type.
+ */
+ Object.defineProperty(this, 'projection', {
+ get: function () {
+ return this._projection;
+ },
+ set: function (type) {
+ if (!this.constructor.projection[type]) {
+ throw new Error('Unsupported projection type: ' + type);
+ }
+ if (type !== this._projection) {
+ this._projection = type;
+ this._createProj();
+ this._update();
+ this.geoTrigger(geo.event.camera.projection, {
+ camera: this,
+ projection: type
+ });
+ }
+ }
+ });
+
+ /**
+ * Getter for the projection matrix (when applicable).
+ * This generally shouldn't be modified directly because
+ * the rest of the code assumes that the clipping bounds
+ * are [-1, -1, -1] to [1, 1, 1] in camera coordinates.
+ */
+ Object.defineProperty(this, 'projectionMatrix', {
+ get: function () {
+ return this._proj;
+ }
+ });
+
+ /**
+ * Getter for the transform matrix.
+ */
+ Object.defineProperty(this, 'transform', {
+ get: function () {
+ return this._transform;
+ }
+ });
+
+ /**
+ * Getter for the inverse transform matrix.
+ */
+ Object.defineProperty(this, 'inverse', {
+ get: function () {
+ return this._inverse;
+ }
+ });
+
+ /**
+ * Getter/setter for the viewport.
+ */
+ Object.defineProperty(this, 'viewport', {
+ get: function () {
+ return {width: this._viewport.width, height: this._viewport.height};
+ },
+ set: function (viewport) {
+ if (!(viewport.width > 0 &&
+ viewport.height > 0)) {
+ throw new Error('Invalid viewport dimensions');
+ }
+ if (viewport.width === this._viewport.width &&
+ viewport.height === this._viewport.height) {
+ return;
+ }
+
+ // apply scaling to the view matrix to account for the new aspect ratio
+ // without changing the apparent zoom level
+ if (this._viewport.width && this._viewport.height) {
+ this._scale(
+ vec3.fromValues(
+ this._viewport.width / viewport.width,
+ this._viewport.height / viewport.height,
+ 1
+ )
+ );
+
+ // translate by half the difference to keep the center the same
+ this._translate(
+ vec3.fromValues(
+ (viewport.width - this._viewport.width) / 2,
+ (viewport.height - this._viewport.height) / 2,
+ 0
+ )
+ );
+ }
+
+ this._viewport = {width: viewport.width, height: viewport.height};
+ this._update();
+ this.geoTrigger(geo.event.camera.viewport, {
+ camera: this,
+ viewport: this.viewport
+ });
+ }
+ });
+
+ /**
+ * Reset the view matrix to its initial (identity) state.
+ * @protected
+ * @returns {this} Chainable
+ */
+ this._resetView = function () {
+ mat4.identity(this._view);
+ return this;
+ };
+
+ /**
+ * Uses `mat4.translate` to translate the camera by the given vector amount.
+ * @protected
+ * @param {vec3} offset The camera translation vector
+ * @returns {this} Chainable
+ */
+ this._translate = function (offset) {
+ mat4.translate(this._view, this._view, offset);
+ };
+
+ /**
+ * Uses `mat4.scale` to scale the camera by the given vector amount.
+ * @protected
+ * @param {vec3} scale The scaling vector
+ * @returns {this} Chainable
+ */
+ this._scale = function (scale) {
+ mat4.scale(this._view, this._view, scale);
+ };
+
+ /**
+ * Project a vec4 from world space into clipped space [-1, 1] in place
+ * @protected
+ * @param {vec4} point The point in world coordinates (mutated)
+ * @returns {vec4} The point in clip space coordinates
+ */
+ this._worldToClip4 = function (point) {
+ return geo.camera.applyTransform(this._transform, point);
+ };
+
+ /**
+ * Project a vec4 from clipped space into world space in place
+ * @protected
+ * @param {vec4} point The point in clipped coordinates (mutated)
+ * @returns {vec4} The point in world space coordinates
+ */
+ this._clipToWorld4 = function (point) {
+ return geo.camera.applyTransform(this._inverse, point);
+ };
+
+ /**
+ * Apply the camera's projection transform to the given point.
+ * @param {vec4} pt a point in clipped coordinates
+ * @returns {vec4} the point in normalized coordinates
+ */
+ this.applyProjection = function (pt) {
+ var w;
+ if (this._projection === 'perspective') {
+ w = 1 / (pt[3] || 1);
+ pt[0] = w * pt[0];
+ pt[1] = w * pt[1];
+ pt[2] = w * pt[2];
+ pt[3] = w;
+ } else {
+ pt[3] = 1;
+ }
+ return pt;
+ };
+
+ /**
+ * Unapply the camera's projection transform from the given point.
+ * @param {vec4} pt a point in normalized coordinates
+ * @returns {vec4} the point in clipped coordinates
+ */
+ this.unapplyProjection = function (pt) {
+ var w;
+ if (this._projection === 'perspective') {
+ w = pt[3] || 1;
+ pt[0] = w * pt[0];
+ pt[1] = w * pt[1];
+ pt[2] = w * pt[2];
+ pt[3] = w;
+ } else {
+ pt[3] = 1;
+ }
+ return pt;
+ };
+
+
+ /**
+ * Project a vec4 from world space into viewport space.
+ * @param {vec4} point The point in world coordinates (mutated)
+ * @returns {vec4} The point in display coordinates
+ *
+ * @note For the moment, this computation assumes the following:
+ * * point[3] > 0
+ * * depth range [0, 1]
+ *
+ * The clip space z and w coordinates are returned with the window
+ * x/y coordinates.
+ */
+ this.worldToDisplay4 = function (point) {
+ // This is because z = 0 is the far plane exposed to the user, but
+ // internally the far plane is at -2.
+ point[2] -= 2;
+
+ // convert to clip space
+ this._worldToClip4(point);
+
+ // apply projection specific transformation
+ point = this.applyProjection(point);
+
+ // convert to display space
+ point[0] = this._viewport.width * (1 + point[0]) / 2.0;
+ point[1] = this._viewport.height * (1 - point[1]) / 2.0;
+ point[2] = (1 + point[2]) / 2.0;
+ return point;
+ };
+
+ /**
+ * Project a vec4 from display space into world space in place.
+ * @param {vec4} point The point in display coordinates (mutated)
+ * @returns {vec4} The point in world space coordinates
+ *
+ * @note For the moment, this computation assumes the following:
+ * * point[3] > 0
+ * * depth range [0, 1]
+ */
+ this.displayToWorld4 = function (point) {
+ // convert to clip space
+ point[0] = 2 * point[0] / this._viewport.width - 1;
+ point[1] = -2 * point[1] / this._viewport.height + 1;
+ point[2] = 2 * point[2] - 1;
+
+ // invert projection transform
+ point = this.unapplyProjection(point);
+
+ // convert to world coordinates
+ this._clipToWorld4(point);
+
+ // move far surface to z = 0
+ point[2] += 2;
+ return point;
+ };
+
+ /**
+ * Project a point object from world space into viewport space.
+ * @param {object} point The point in world coordinates
+ * @param {number} point.x
+ * @param {number} point.y
+ * @returns {object} The point in display coordinates
+ */
+ this.worldToDisplay = function (point) {
+ // define some magic numbers:
+ var z = 0, // z coordinate of the surface in world coordinates
+ w = 1; // enables perspective divide (i.e. for point conversion)
+ point = this.worldToDisplay4(
+ [point.x, point.y, z, w]
+ );
+ return {x: point[0], y: point[1], z: point[2]};
+ };
+
+ /**
+ * Project a point object from viewport space into world space.
+ * @param {object} point The point in display coordinates
+ * @param {number} point.x
+ * @param {number} point.y
+ * @returns {object} The point in world coordinates
+ */
+ this.displayToWorld = function (point) {
+ // define some magic numbers:
+ var z = 1, // the z coordinate of the surface
+ w = 2; // perspective divide at z = 1
+ point = this.displayToWorld4(
+ [point.x, point.y, z, w]
+ );
+ return {x: point[0], y: point[1]};
+ };
+
+ /**
+ * Calculate the current bounds in world coordinates from the
+ * current view matrix. This computes a matrix vector multiplication
+ * so the result is cached for public facing methods.
+ *
+ * @protected
+ * @returns {object} bounds object
+ */
+ this._getBounds = function () {
+ var pt, bds = {};
+
+
+ // get lower bounds
+ pt = this.displayToWorld({
+ x: 0, y: this._viewport.height
+ });
+ bds.left = pt.x;
+ bds.bottom = pt.y;
+
+ // get upper bounds
+ pt = this.displayToWorld({
+ x: this._viewport.width, y: 0
+ });
+ bds.right = pt.x;
+ bds.top = pt.y;
+
+ return bds;
+ };
+
+ /**
+ * Sets the view matrix so that the given world bounds
+ * are in view. To account for the viewport aspect ratio,
+ * the resulting bounds may be larger in width or height than
+ * the requested bound, but should be centered in the frame.
+ *
+ * @protected
+ * @param {object} bounds
+ * @param {number} bounds.left
+ * @param {number} bounds.right
+ * @param {number} bounds.bottom
+ * @param {number} bounds.top
+ * @param {number?} bounds.near Currently ignored
+ * @param {number?} bounds.far Currently ignored
+ * @return {this} Chainable
+ */
+ this._setBounds = function (bounds) {
+
+ var translate = vec3.create(),
+ scale = vec3.create(),
+ c_ar, v_ar, w, h;
+
+ bounds.near = bounds.near || 0;
+ bounds.far = bounds.far || 1;
+
+ // reset view to the identity
+ this._resetView();
+
+ w = Math.abs(bounds.right - bounds.left);
+ h = Math.abs(bounds.top - bounds.bottom);
+ c_ar = w / h;
+ v_ar = this._viewport.width / this._viewport.height;
+
+ if (c_ar >= v_ar) {
+ // grow camera bounds vertically
+ h = w / v_ar;
+ scale[0] = 2 / w;
+ scale[1] = 2 / h;
+ } else {
+ // grow bounds horizontally
+ w = h * v_ar;
+ scale[0] = 2 / w;
+ scale[1] = 2 / h;
+ }
+
+ scale[2] = 1;
+ this._scale(scale);
+
+ // translate to the new center.
+ translate[0] = -(bounds.left + bounds.right) / 2;
+ translate[1] = -(bounds.bottom + bounds.top) / 2;
+ translate[2] = 0;
+
+ this._translate(translate);
+ return this;
+ };
+
+ /**
+ * Pans the view matrix by the given amount.
+ *
+ * @param {object} offset The delta in world space coordinates.
+ * @param {number} offset.x
+ * @param {number} offset.y
+ * @param {number} [offset.z=0]
+ */
+ this.pan = function (offset) {
+ this._translate(vec3.fromValues(
+ offset.x,
+ offset.y,
+ offset.z || 0
+ ));
+ this._update();
+ };
+
+ /**
+ * Zooms the view matrix by the given amount.
+ *
+ * @param {number} zoom The zoom scale to apply
+ */
+ this.zoom = function (zoom) {
+ mat4.scale(this._view, this._view,
+ vec3.fromValues(
+ zoom,
+ zoom,
+ zoom
+ )
+ );
+ this._update();
+ };
+
+ /**
+ * Returns a CSS transform that converts (by default) from world coordinates
+ * into display coordinates. This allows users of this module to
+ * position elements using world coordinates directly inside DOM
+ * elements.
+ *
+ * @note This transform will not take into account projection specific
+ * transforms. For perspective projections, one can use the properties
+ * `perspective` and `perspective-origin` to apply the projection
+ * in css directly.
+ *
+ * @param {string} transform The transform to return
+ * * display
+ * * world
+ * @returns {string} The css transform string
+ */
+ this.css = function (transform) {
+ var m;
+ switch ((transform || '').toLowerCase()) {
+ case 'display':
+ case '':
+ m = this.display;
+ break;
+ case 'world':
+ m = this.world;
+ break;
+ default:
+ throw new Error('Unknown transform ' + transform);
+ }
+ return geo.camera.css(m);
+ };
+
+ /**
+ * Represent a glmatrix as a pretty-printed string.
+ * @param {mat4} mat A 4 x 4 matrix
+ * @param {number} prec The number of decimal places
+ * @returns {string}
+ */
+ this.ppMatrix = function (mat, prec) {
+ var t = mat;
+ prec = prec || 2;
+ function f(i) {
+ var d = t[i], s = d.toExponential(prec);
+ if (d >= 0) {
+ s = ' ' + s;
+ }
+ return s;
+ }
+ return [
+ [f(0), f(4), f(8), f(12)].join(' '),
+ [f(1), f(5), f(9), f(13)].join(' '),
+ [f(2), f(6), f(10), f(14)].join(' '),
+ [f(3), f(7), f(11), f(15)].join(' ')
+ ].join('\n');
+ };
+
+ /**
+ * Pretty print the transform matrix.
+ */
+ this.toString = function () {
+ return this.ppMatrix(this._transform);
+ };
+
+ /**
+ * Return a debugging string of the current camera state.
+ */
+ this.debug = function () {
+ return [
+ 'bounds',
+ JSON.stringify(this.bounds),
+ 'view:',
+ this.ppMatrix(this._view),
+ 'projection:',
+ this.ppMatrix(this._proj),
+ 'transform:',
+ this.ppMatrix(this._transform)
+ ].join('\n');
+ };
+
+ /**
+ * Represent the value of the camera as its transform matrix.
+ */
+ this.valueOf = function () {
+ return this._transform;
+ };
+
+ // initialize the view matrix
+ this._resetView();
+
+ // set up the projection matrix
+ this.projection = spec.projection || 'parallel';
+
+ // initialize the viewport
+ if (spec.viewport) {
+ this.viewport = spec.viewport;
+ }
+
+ // trigger an initial update to set up the camera state
+ this._update();
+
+ return this;
+ };
+
+ /**
+ * Supported projection types.
+ */
+ geo.camera.projection = {
+ perspective: true,
+ parallel: true
+ };
+
+ /**
+ * Camera clipping bounds, probably shouldn't be modified.
+ */
+ geo.camera.bounds = {
+ left: -1,
+ right: 1,
+ top: 1,
+ bottom: -1,
+ far: -2,
+ near: -1
+ };
+
+ /**
+ * Output a mat4 as a css transform.
+ * @param {mat4} t A matrix transform
+ * @returns {string} A css transform string
+ */
+ geo.camera.css = function (t) {
+ return (
+ 'matrix3d(' +
+ [
+ t[0].toFixed(20),
+ t[1].toFixed(20),
+ t[2].toFixed(20),
+ t[3].toFixed(20),
+ t[4].toFixed(20),
+ t[5].toFixed(20),
+ t[6].toFixed(20),
+ t[7].toFixed(20),
+ t[8].toFixed(20),
+ t[9].toFixed(20),
+ t[10].toFixed(20),
+ t[11].toFixed(20),
+ t[12].toFixed(20),
+ t[13].toFixed(20),
+ t[14].toFixed(20),
+ t[15].toFixed(20)
+ ].join(',') +
+ ')'
+ );
+ };
+
+ /**
+ * Generate a mat4 representing an affine coordinate transformation.
+ *
+ * For the following affine transform:
+ *
+ * x |-> m * (x + a) + b
+ *
+ * applies the css transform:
+ *
+ * translate(b) scale(m) translate(a)
+ *
+ * @param {object?} pre Coordinate offset **before** scaling
+ * @param {object?} scale Coordinate scaling
+ * @param {object?} post Coordinate offset **after** scaling
+ * @returns {mat4} The new transform matrix
+ */
+ geo.camera.affine = function (pre, scale, post) {
+ var mat = mat4.create();
+
+ // Note: mat4 operations are applied to the right side of the current
+ // transform, so the first applied here is the last applied to the
+ // coordinate.
+ if (post) {
+ mat4.translate(mat, mat, [post.x || 0, post.y || 0, post.z || 0]);
+ }
+ if (scale) {
+ mat4.scale(mat, mat, [scale.x || 1, scale.y || 1, scale.z || 1]);
+ }
+ if (pre) {
+ mat4.translate(mat, mat, [pre.x || 0, pre.y || 0, pre.z || 0]);
+ }
+ return mat;
+ };
+
+ /**
+ * Apply the given transform matrix to a point in place.
+ * @param {mat4} t
+ * @param {vec4} pt
+ * @returns {vec4}
+ */
+ geo.camera.applyTransform = function (t, pt) {
+ return vec4.transformMat4(pt, pt, t);
+ };
+
+ /**
+ * Combine two transforms by multiplying their matrix representations.
+ * @note The second transform provided will be the first applied in the
+ * coordinate transform.
+ * @param {mat4} A
+ * @param {mat4} B
+ * @returns {mat4} A * B
+ */
+ geo.camera.combine = function (A, B) {
+ return mat4.mul(mat4.create(), A, B);
+ };
+
+ inherit(geo.camera, geo.object);
+})();
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * @class
+ * @extends geo.sceneObject
+ * @param {Object?} arg An options argument
+ * @param {string} arg.attribution An attribution string to display
+ * @param {number} arg.zIndex The z-index to assign to the layer (defaults
+ * to the index of the layer inside the map)
+ * @returns {geo.layer}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.layer = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof geo.layer)) {
+ return new geo.layer(arg);
+ }
+ arg = arg || {};
+ geo.sceneObject.call(this, arg);
+
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ s_exit = this._exit,
+ m_id = arg.id === undefined ? geo.layer.newLayerId() : arg.id,
+ m_name = '',
+ m_map = arg.map === undefined ? null : arg.map,
+ m_node = null,
+ m_canvas = null,
+ m_renderer = null,
+ m_initialized = false,
+ m_rendererName = arg.renderer === undefined ? 'vgl' : arg.renderer,
+ m_dataTime = geo.timestamp(),
+ m_updateTime = geo.timestamp(),
+ m_sticky = arg.sticky === undefined ? true : arg.sticky,
+ m_active = arg.active === undefined ? true : arg.active,
+ m_opacity = arg.opacity === undefined ? 1 : arg.opacity,
+ m_attribution = arg.attribution || null,
+ m_zIndex;
+
+ if (!m_map) {
+ throw new Error('Layers must be initialized on a map.');
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get the name of the renderer.
+ *
+ * @returns {string}
*/
////////////////////////////////////////////////////////////////////////////
- this.node = function () {
- return m_node;
+ this.rendererName = function () {
+ return m_rendererName;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set id of the layer
+ * Get or set the z-index of the layer. The z-index controls the display
+ * order of the layers in much the same way as the CSS z-index property.
*
- * @returns {String}
+ * @param {number} [zIndex] The new z-index
+ * @returns {number|this}
*/
////////////////////////////////////////////////////////////////////////////
- this.id = function (val) {
- if (val === undefined) {
- return m_id;
+ this.zIndex = function (zIndex) {
+ if (zIndex === undefined) {
+ return m_zIndex;
}
- m_id = geo.newLayerId();
- m_this.modified();
+ m_zIndex = zIndex;
+ m_node.css('z-index', m_zIndex);
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set name of the layer
+ * Bring the layer above the given number of layers. This will rotate the
+ * current z-indices for this and the next `n` layers.
*
- * @returns {String}
+ * @param {number} [n=1] The number of positions to move
+ * @returns {this}
*/
////////////////////////////////////////////////////////////////////////////
- this.name = function (val) {
- if (val === undefined) {
- return m_name;
+ this.moveUp = function (n) {
+ var order, i, me = null, tmp, sign;
+
+ // set the default
+ if (n === undefined) {
+ n = 1;
+ }
+
+ // set the sort direction that controls if we are moving up
+ // or down the z-index
+ sign = 1;
+ if (n < 0) {
+ sign = -1;
+ n = -n;
+ }
+
+ // get a sorted list of layers
+ order = m_this.map().layers().sort(
+ function (a, b) { return sign * (a.zIndex() - b.zIndex()); }
+ );
+
+ for (i = 0; i < order.length; i += 1) {
+ if (me === null) {
+ // loop until we get to the current layer
+ if (order[i] === m_this) {
+ me = i;
+ }
+ } else if (i - me <= n) {
+ // swap the next n layers
+ tmp = m_this.zIndex();
+ m_this.zIndex(order[i].zIndex());
+ order[i].zIndex(tmp);
+ } else {
+ // all the swaps are done now
+ break;
+ }
}
- m_name = val;
- m_this.modified();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set opacity of the layer
+ * Bring the layer below the given number of layers. This will rotate the
+ * current z-indices for this and the previous `n` layers.
*
- * @returns {Number}
+ * @param {number} [n=1] The number of positions to move
+ * @returns {this}
*/
////////////////////////////////////////////////////////////////////////////
- this.opacity = function (val) {
- if (val === undefined) {
- return m_style.opacity;
+ this.moveDown = function (n) {
+ if (n === undefined) {
+ n = 1;
}
- m_style.opacity = val;
- m_this.modified();
- return m_this;
+ return m_this.moveUp(-n);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set visibility of the layer
+ * Bring the layer to the top of the map layers.
+ *
+ * @returns {this}
*/
////////////////////////////////////////////////////////////////////////////
- this.visible = function (val) {
- if (val === undefined) {
- return m_style.visible;
- }
- m_style.visible = val;
- m_this.modified();
- return m_this;
+ this.moveToTop = function () {
+ return m_this.moveUp(m_this.map().children().length - 1);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set bin of the layer
+ * Bring the layer to the bottom of the map layers.
*
- * @returns {Number}
+ * @returns {this}
*/
////////////////////////////////////////////////////////////////////////////
- this.bin = function (val) {
- if (val === undefined) {
- return m_style.bin;
- }
- m_style.bin = val;
- m_this.modified();
- return m_this;
+ this.moveToBottom = function () {
+ return m_this.moveDown(m_this.map().children().length - 1);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set projection of the layer
+ * Get whether or not the layer is sticky (navigates with the map).
+ *
+ * @returns {Boolean}
*/
////////////////////////////////////////////////////////////////////////////
- this.gcs = function (val) {
- if (val === undefined) {
- return m_gcs;
- }
- m_gcs = val;
- m_this.modified();
- return m_this;
+ this.sticky = function () {
+ return m_sticky;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Transform layer to the reference layer gcs
+ * Get whether or not the layer is active. An active layer will receive
+ * native mouse when the layer is on top. Non-active layers will never
+ * receive native mouse events.
+ *
+ * @returns {Boolean}
*/
////////////////////////////////////////////////////////////////////////////
- this.transform = function (val) {
- geo.transform.transformLayer(val, m_this, m_map.baseLayer());
- return m_this;
+ this.active = function () {
+ return m_active;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set root node of the layer
+ *
+ * @returns {div}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.node = function () {
+ return m_node;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set time range of the layer
+ * Get/Set id of the layer
+ *
+ * @returns {String}
*/
////////////////////////////////////////////////////////////////////////////
- this.timeRange = function (val) {
+ this.id = function (val) {
if (val === undefined) {
- return m_timeRange;
+ return m_id;
}
- m_timeRange = val;
+ m_id = geo.newLayerId();
m_this.modified();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set source of the layer
+ * Get/Set name of the layer
+ *
+ * @returns {String}
*/
////////////////////////////////////////////////////////////////////////////
- this.source = function (val) {
+ this.name = function (val) {
if (val === undefined) {
- return m_source;
+ return m_name;
}
- m_source = val;
+ m_name = val;
m_this.modified();
return m_this;
};
@@ -14891,14 +15851,8 @@ geo.layer = function (arg) {
* Get/Set map of the layer
*/
////////////////////////////////////////////////////////////////////////////
- this.map = function (val) {
- if (val === undefined) {
- return m_map;
- }
- m_map = val;
- m_map.node().append(m_node);
- m_this.modified();
- return m_this;
+ this.map = function () {
+ return m_map;
};
////////////////////////////////////////////////////////////////////////////
@@ -14920,15 +15874,6 @@ geo.layer = function (arg) {
return m_canvas;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get viewport of the layer
- */
- ////////////////////////////////////////////////////////////////////////////
- this.viewport = function () {
- return [m_x, m_y, m_width, m_height];
- };
-
////////////////////////////////////////////////////////////////////////////
/**
* Return last time data got changed
@@ -14947,37 +15892,6 @@ geo.layer = function (arg) {
return m_updateTime;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Return the modified time for the last draw call that did something
- */
- ////////////////////////////////////////////////////////////////////////////
- this.drawTime = function () {
- return m_drawTime;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Run query and return results for it
- */
- ////////////////////////////////////////////////////////////////////////////
- this.query = function () {
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set layer as the reference layer
- */
- ////////////////////////////////////////////////////////////////////////////
- this.referenceLayer = function (val) {
- if (val !== undefined) {
- m_isReference = val;
- m_this.modified();
- return m_this;
- }
- return m_isReference;
- };
-
////////////////////////////////////////////////////////////////////////////
/**
* Get/Set if the layer has been initialized
@@ -14993,21 +15907,29 @@ geo.layer = function (arg) {
////////////////////////////////////////////////////////////////////////////
/**
- * Transform a point or array of points in GCS space to
- * local space of the layer
+ * Transform coordinates from world coordinates into a local coordinate
+ * system specific to the underlying renderer. This method is exposed
+ * to allow direct access the rendering context, but otherwise should
+ * not be called directly. The default implementation is the identity
+ * operator.
*/
////////////////////////////////////////////////////////////////////////////
this.toLocal = function (input) {
+ if (m_this._toLocalMatrix) {
+ geo.camera.applyTransform(m_this._toLocalMatrix, input);
+ }
return input;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Transform a point or array of points in local space to
- * latitude-longitude space
+ * Transform coordinates from a local coordinate system to world coordinates.
*/
////////////////////////////////////////////////////////////////////////////
this.fromLocal = function (input) {
+ if (m_this._fromLocalMatrix) {
+ geo.camera.applyTransform(m_this._fromLocalMatrix, input);
+ }
return input;
};
@@ -15041,21 +15963,18 @@ geo.layer = function (arg) {
return m_this;
}
- // Create top level div for the layer
- m_node = $(document.createElement("div"));
- m_node.attr("id", m_name);
- m_node.css("position", "absolute");
-
- if (m_map) {
- m_map.node().append(m_node);
-
- }
+ m_map.node().append(m_node);
/* Pass along the arguments, but not the map reference */
var options = $.extend({}, arg);
delete options.map;
- // Share context if have valid one
- if (m_canvas) {
+
+ if (m_rendererName === null) {
+ // if given a "null" renderer, then pass the map element as the
+ // canvas
+ m_renderer = null;
+ m_canvas = m_node;
+ } else if (m_canvas) { // Share context if have valid one
m_renderer = geo.createRenderer(m_rendererName, m_this, m_canvas,
options);
} else {
@@ -15065,11 +15984,24 @@ geo.layer = function (arg) {
}
if (!m_this.active()) {
- m_node.css("pointerEvents", "none");
+ m_node.css('pointerEvents', 'none');
}
m_initialized = true;
+ /// Bind events to handlers
+ m_this.geoOn(geo.event.resize, function (event) {
+ m_this._update({event: event});
+ });
+
+ m_this.geoOn(geo.event.pan, function (event) {
+ m_this._update({event: event});
+ });
+
+ m_this.geoOn(geo.event.zoom, function (event) {
+ m_this._update({event: event});
+ });
+
return m_this;
};
@@ -15079,10 +16011,12 @@ geo.layer = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
this._exit = function () {
- m_renderer._exit();
+ m_this.geoOff();
+ if (m_renderer) {
+ m_renderer._exit();
+ }
m_node.off();
m_node.remove();
- m_node = null;
arg = {};
m_canvas = null;
m_renderer = null;
@@ -15099,43 +16033,55 @@ geo.layer = function (arg) {
////////////////////////////////////////////////////////////////////////////
/**
- * Respond to resize event
+ * Return the width of the layer in pixels.
+ * **DEPRECIATED: use map.size instead.
*/
////////////////////////////////////////////////////////////////////////////
- this._resize = function (x, y, w, h) {
- m_x = x;
- m_y = y;
- m_width = w;
- m_height = h;
- m_node.width(w);
- m_node.height(h);
-
- m_this.modified();
- m_this.geoTrigger(geo.event.resize,
- {x: x, y: y, width: m_width, height: m_height});
-
- return m_this;
+ this.width = function () {
+ return m_this.map().size().width;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Return the width of the layer in pixels
+ * Return the height of the layer in pixels
+ * **DEPRECIATED: use map.size instead.
*/
////////////////////////////////////////////////////////////////////////////
- this.width = function () {
- return m_width;
+ this.height = function () {
+ return m_this.map().size().height;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Return the height of the layer in pixels
+ * Get or set the current layer opacity.
*/
////////////////////////////////////////////////////////////////////////////
- this.height = function () {
- return m_height;
+ this.opacity = function (opac) {
+ if (opac !== undefined) {
+ m_opacity = opac;
+ m_node.css('opacity', m_opacity);
+ return m_this;
+ }
+ return m_opacity;
};
- return this;
+ if (arg.zIndex === undefined) {
+ arg.zIndex = m_map.children().length;
+ }
+ m_zIndex = arg.zIndex;
+
+ // Create top level div for the layer
+ m_node = $(document.createElement('div'));
+ m_node.attr('id', m_name);
+ m_node.css('position', 'absolute');
+ m_node.css('width', '100%');
+ m_node.css('height', '100%');
+ m_this.opacity(m_opacity);
+
+ // set the z-index
+ m_this.zIndex(m_zIndex);
+
+ return m_this;
};
/**
@@ -15145,7 +16091,7 @@ geo.layer = function (arg) {
* @returns {number}
*/
geo.layer.newLayerId = (function () {
- "use strict";
+ 'use strict';
var currentId = 1;
return function () {
var id = currentId;
@@ -15159,11 +16105,11 @@ geo.layer.newLayerId = (function () {
* General object specification for feature types.
* @typedef geo.layer.spec
* @type {object}
- * @property {string} [type="feature"] For feature compatibility
+ * @property {string} [type='feature'] For feature compatibility
* with more than one kind of creatable layer
* @property {object[]} [data=[]] The default data array to
* apply to each feature if none exists
- * @property {string} [renderer="vgl"] The renderer to use
+ * @property {string} [renderer='vgl'] The renderer to use
* @property {geo.feature.spec[]} [features=[]] Features
* to add to the layer
*/
@@ -15176,26 +16122,26 @@ geo.layer.newLayerId = (function () {
* @returns {geo.layer|null}
*/
geo.layer.create = function (map, spec) {
- "use strict";
+ 'use strict';
spec = spec || {};
// add osmLayer later
- spec.type = "feature";
- if (spec.type !== "feature") {
- console.warn("Unsupported layer type");
+ spec.type = 'feature';
+ if (spec.type !== 'feature') {
+ console.warn('Unsupported layer type');
return null;
}
- spec.renderer = spec.renderer || "vgl";
- if (spec.renderer !== "d3" && spec.renderer !== "vgl") {
- console.warn("Invalid renderer");
+ spec.renderer = spec.renderer || 'vgl';
+ if (spec.renderer !== 'd3' && spec.renderer !== 'vgl') {
+ console.warn('Invalid renderer');
return null;
}
var layer = map.createLayer(spec.type, spec);
if (!layer) {
- console.warn("Unable to create a layer");
+ console.warn('Unable to create a layer');
return null;
}
@@ -15301,8 +16247,6 @@ geo.featureLayer = function (arg) {
////////////////////////////////////////////////////////////////////////////
/**
* Initialize
- *
- * Do not call parent _init method as its already been executed
*/
////////////////////////////////////////////////////////////////////////////
this._init = function () {
@@ -15322,12 +16266,16 @@ geo.featureLayer = function (arg) {
m_this.geoOn(geo.event.pan, function (event) {
m_this._update({event: event});
- m_this.renderer()._render();
+ if (m_this.renderer()) {
+ m_this.renderer()._render();
+ }
});
m_this.geoOn(geo.event.zoom, function (event) {
m_this._update({event: event});
- m_this.renderer()._render();
+ if (m_this.renderer()) {
+ m_this.renderer()._render();
+ }
});
return m_this;
@@ -15348,7 +16296,7 @@ geo.featureLayer = function (arg) {
/// Call base class update
s_update.call(m_this, request);
- if (!m_this.source() && m_features && m_features.length === 0) {
+ if (m_features && m_features.length === 0) {
console.log("[info] No valid data source found.");
return;
}
@@ -15389,7 +16337,9 @@ geo.featureLayer = function (arg) {
// Now call render on the renderer. In certain cases it may not do
// anything if the if the child objects are drawn on the screen already.
- m_this.renderer()._render();
+ if (m_this.renderer()) {
+ m_this.renderer()._render();
+ }
return m_this;
};
@@ -15436,8 +16386,8 @@ geo.registerLayer("feature", geo.featureLayer);
* The following properties are common to all event objects:
*
* @namespace
- * @property type {string} The event type that was triggered
- * @property geo {object} A universal event object for controlling propagation
+ * @property {string} type The event type that was triggered
+ * @property {object} geo A universal event object for controlling propagation
*
* @example
* map.geoOn(geo.event.layerAdd, function (event) {
@@ -15457,33 +16407,32 @@ geo.event = {};
// The following were not triggered nor used anywhere. Removing until their
// purpose is defined more clearly.
//
-// geo.event.update = "geo_update";
-// geo.event.opacityUpdate = "geo_opacityUpdate";
-// geo.event.layerToggle = "geo_layerToggle";
-// geo.event.layerSelect = "geo_layerSelect";
-// geo.event.layerUnselect = "geo_layerUnselect";
-// geo.event.rotate = "geo_rotate";
-// geo.event.query = "geo_query";
+// geo.event.update = 'geo_update';
+// geo.event.opacityUpdate = 'geo_opacityUpdate';
+// geo.event.layerToggle = 'geo_layerToggle';
+// geo.event.layerSelect = 'geo_layerSelect';
+// geo.event.layerUnselect = 'geo_layerUnselect';
+// geo.event.query = 'geo_query';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered when a layer is added to the map.
*
- * @property target {geo.map} The current map
- * @property layer {geo.layer} The new layer
+ * @property {geo.map} target The current map
+ * @property {geo.layer} layer The new layer
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.layerAdd = "geo_layerAdd";
+geo.event.layerAdd = 'geo_layerAdd';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered when a layer is removed from the map.
*
- * @property target {geo.map} The current map
- * @property layer {geo.layer} The old layer
+ * @property {geo.map} target The current map
+ * @property {geo.layer} layer The old layer
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.layerRemove = "geo_layerRemove";
+geo.event.layerRemove = 'geo_layerRemove';
//////////////////////////////////////////////////////////////////////////////
/**
@@ -15491,77 +16440,122 @@ geo.event.layerRemove = "geo_layerRemove";
* triggered on the map itself. Instead it is triggered individually on
* layers, starting with the base layer.
*
- * @property zoomLevel {Number} New zoom level
- * @property screenPosition {object} The screen position of mouse pointer
+ * @property {number} zoomLevel New zoom level
+ * @property {object} screenPosition The screen position of mouse pointer
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.event.zoom = 'geo_zoom';
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Triggered when the map is rotated around the map center (pointing downward
+ * so that positive angles are clockwise rotations).
+ *
+ * @property {number} angle The angle of the rotation in radians
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.zoom = "geo_zoom";
+geo.event.rotate = 'geo_rotate';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered when the map is panned either by user interaction or map
* transition.
*
- * @property screenDelta {object} The number of pixels to pan the map by
- * @property center {object} The new map center
+ * @property {object} screenDelta The number of pixels to pan the map by
+ * @property {object} center The new map center
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.pan = "geo_pan";
+geo.event.pan = 'geo_pan';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered when the map's canvas is resized.
*
- * @property width {Number} The new width in pixels
- * @property height {Number} The new height in pixels
+ * @property {number} width The new width in pixels
+ * @property {number} height The new height in pixels
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.event.resize = 'geo_resize';
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Triggered when the world coordinate system changes. Data in GCS
+ * coordinates can be transformed by the following formulas:
+ *
+ * x <- (x - origin.x) * scale.x
+ * y <- (y - origin.y) * scale.y
+ * z <- (z - origin.z) * scale.z
+ *
+ * Data in world coordinates can be updated using the following formulas:
+ *
+ * x <- (x * scaleChange.x - origin.x * (scale.x + scaleChange.x)
+ * - scale.x * originChange.x) * scale.x / scaleChange.x
+ * y <- (y * scaleChange.y - origin.y * (scale.y + scaleChange.y)
+ * - scale.y * originChange.y) * scale.y / scaleChange.y
+ * z <- (z * scaleChange.z - origin.z * (scale.z + scaleChange.z)
+ * - scale.z * originChange.z) * scale.z / scaleChange.z
+ *
+ * @property {geo.map} map The map whose coordinates changed
+ * @property {object} origin The new origin in GCS coordinates
+ * @property {number} origin.x
+ * @property {number} origin.y
+ * @property {number} origin.z
+ * @property {object} scale The new scale factor
+ * @property {number} scale.x
+ * @property {number} scale.y
+ * @property {number} scale.z
+ * @property {object} originChange Relative change from the old origin defined
+ * as `origin - oldorigin`.
+ * @property {object} scaleChange Relative change from the old scale defined
+ * as `scale / oldscale`.
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.resize = "geo_resize";
+geo.event.worldChanged = 'geo_worldChanged';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered on every call to {@link geo.map#draw} before the map is rendered.
*
- * @property target {geo.map} The current map
+ * @property {geo.map} target The current map
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.draw = "geo_draw";
+geo.event.draw = 'geo_draw';
//////////////////////////////////////////////////////////////////////////////
/**
* Triggered on every call to {@link geo.map#draw} after the map is rendered.
*
- * @property target {geo.map} The current map
+ * @property {geo.map} target The current map
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.drawEnd = "geo_drawEnd";
+geo.event.drawEnd = 'geo_drawEnd';
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered on every "mousemove" over the map's DOM element. The event
+ * Triggered on every 'mousemove' over the map's DOM element. The event
* object extends {@link geo.mouseState}.
* @mixes geo.mouseState
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.mousemove = "geo_mousemove";
+geo.event.mousemove = 'geo_mousemove';
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered on every "mousedown" over the map's DOM element. The event
+ * Triggered on every 'mousedown' over the map's DOM element. The event
* object extends {@link geo.mouseState}.
* @mixes geo.mouseState
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.mouseclick = "geo_mouseclick";
+geo.event.mouseclick = 'geo_mouseclick';
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered on every "mousemove" during a brushing selection.
+ * Triggered on every 'mousemove' during a brushing selection.
* The event object extends {@link geo.brushSelection}.
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.brush = "geo_brush";
+geo.event.brush = 'geo_brush';
//////////////////////////////////////////////////////////////////////////////
/**
@@ -15570,7 +16564,7 @@ geo.event.brush = "geo_brush";
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.brushend = "geo_brushend";
+geo.event.brushend = 'geo_brushend';
//////////////////////////////////////////////////////////////////////////////
/**
@@ -15579,86 +16573,150 @@ geo.event.brushend = "geo_brushend";
* @mixes geo.brushSelection
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.brushstart = "geo_brushstart";
+geo.event.brushstart = 'geo_brushstart';
+
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Triggered before a map navigation animation begins. Set
+ * event.geo.cancelAnimation
to cancel the animation
+ * of the navigation. This will cause the map to navigate to the
+ * target location immediately. Set event.geo.cancelNavigation
+ * to cancel the navigation completely. The transition options can
+ * be modified in place.
+ *
+ * @property {geo.geoPosition} center The target center
+ * @property {number} zoom The target zoom level
+ * @property {number} duration The duration of the transition in milliseconds
+ * @property {function} ease The easing function
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.event.transitionstart = 'geo_transitionstart';
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Triggered after a map navigation animation ends.
+ *
+ * @property {geo.geoPosition} center The target center
+ * @property {number} zoom The target zoom level
+ * @property {number} duration The duration of the transition in milliseconds
+ * @property {function} ease The easing function
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.event.transitionend = 'geo_transitionend';
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Triggered when the parallel projection mode is changes.
+ *
+ * @property paralellProjection {boolean} True if parallel projection is turned
+ * on.
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.event.parallelprojection = 'geo_parallelprojection';
+
+////////////////////////////////////////////////////////////////////////////
+/**
+ * @namespace
+ */
+////////////////////////////////////////////////////////////////////////////
+geo.event.clock = {
+ play: 'geo_clock_play',
+ stop: 'geo_clock_stop',
+ pause: 'geo_clock_pause',
+ change: 'geo_clock_change'
+};
+
+////////////////////////////////////////////////////////////////////////////
+/**
+ * This event object provides mouse/keyboard events that can be handled
+ * by the features. This provides a similar interface as core events,
+ * but with different names so the events don't interfere. Subclasses
+ * can override this to provide custom events.
+ *
+ * These events will only be triggered on features which were instantiated
+ * with the option 'selectionAPI'.
+ * @namespace
+ */
+////////////////////////////////////////////////////////////////////////////
+geo.event.feature = {
+ mousemove: 'geo_feature_mousemove',
+ mouseover: 'geo_feature_mouseover',
+ mouseout: 'geo_feature_mouseout',
+ mouseon: 'geo_feature_mouseon',
+ mouseoff: 'geo_feature_mouseoff',
+ mouseclick: 'geo_feature_mouseclick',
+ brushend: 'geo_feature_brushend',
+ brush: 'geo_feature_brush'
+};
+////////////////////////////////////////////////////////////////////////////
+/**
+ * These events are triggered by the camera when it's internal state is
+ * mutated.
+ * @namespace
+ */
+////////////////////////////////////////////////////////////////////////////
+geo.event.camera = {};
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered before a map navigation animation begins. Set
- * event.geo.cancelAnimation
to cancel the animation
- * of the navigation. This will cause the map to navigate to the
- * target location immediately. Set event.geo.cancelNavigation
- * to cancel the navigation completely. The transition options can
- * be modified in place.
+ * Triggered after a general view matrix change (any change in the visible
+ * bounds). This is equivalent to the union of pan and zoom.
*
- * @property {geo.geoPosition} center The target center
- * @property {Number} zoom The target zoom level
- * @property {Number} duration The duration of the transition in milliseconds
- * @property {function} ease The easing function
+ * @property {geo.camera} camera The camera instance
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.transitionstart = "geo_transitionstart";
+geo.event.camera.view = 'geo_camera_view';
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered after a map navigation animation ends.
+ * Triggered after a pan in the x/y plane (no zoom level change).
*
- * @property {geo.geoPosition} center The target center
- * @property {Number} zoom The target zoom level
- * @property {Number} duration The duration of the transition in milliseconds
- * @property {function} ease The easing function
+ * @property {geo.camera} camera The camera instance
+ * @property {object} delta The translation delta in world coordinates.
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.transitionend = "geo_transitionend";
+geo.event.camera.pan = 'geo_camera_pan';
//////////////////////////////////////////////////////////////////////////////
/**
- * Triggered when the parallel projection mode is changes.
+ * Triggered after a view matrix change that is not a simple pan. This
+ * includes, but is not limited to, pure zooms.
*
- * @property paralellProjection {boolean} True if parallel projection is turned
- * on.
+ * @property {geo.camera} camera The camera instance
*/
//////////////////////////////////////////////////////////////////////////////
-geo.event.parallelprojection = "geo_parallelprojection";
+geo.event.camera.zoom = 'geo_camera_zoom';
-////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
/**
- * @namespace
+ * Triggered after a projection change.
+ *
+ * @property {geo.camera} camera The camera instance
+ * @property {string} type The projection type ('perspective'|'parallel')
*/
-////////////////////////////////////////////////////////////////////////////
-geo.event.clock = {
- play: "geo_clock_play",
- stop: "geo_clock_stop",
- pause: "geo_clock_pause",
- change: "geo_clock_change"
-};
+//////////////////////////////////////////////////////////////////////////////
+geo.event.camera.projection = 'geo_camera_projection';
-////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
/**
- * This event object provides mouse/keyboard events that can be handled
- * by the features. This provides a similar interface as core events,
- * but with different names so the events don't interfere. Subclasses
- * can override this to provide custom events.
+ * Triggered after a viewport change.
*
- * These events will only be triggered on features which were instantiated
- * with the option "selectionAPI".
- * @namespace
+ * @property {geo.camera} camera The camera instance
+ * @property {object} viewport The new viewport
+ * @property {number} viewport.width The new width
+ * @property {number} viewport.height The new height
*/
-////////////////////////////////////////////////////////////////////////////
-geo.event.feature = {
- mousemove: "geo_feature_mousemove",
- mouseover: "geo_feature_mouseover",
- mouseout: "geo_feature_mouseout",
- mouseon: "geo_feature_mouseon",
- mouseoff: "geo_feature_mouseoff",
- mouseclick: "geo_feature_mouseclick",
- brushend: "geo_feature_brushend",
- brush: "geo_feature_brush"
-};
+//////////////////////////////////////////////////////////////////////////////
+geo.event.camera.viewport = 'geo_camera_viewport';
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of mapInteractor
+ * The mapInteractor class is responsible for handling raw events from the
+ * browser and interpreting them as map navigation interactions. This class
+ * will call the navigation methods on the connected map, which will make
+ * modifications to the camera directly.
*
* @class
* @extends geo.object
@@ -15683,7 +16741,9 @@ geo.mapInteractor = function (args) {
m_wait = false,
m_disableThrottle = true,
m_selectionLayer = null,
- m_selectionPlane = null;
+ m_selectionPlane = null,
+ m_paused = false,
+ m_clickMaybe = false;
// Helper method to decide if the current button/modifiers match a set of
// conditions.
@@ -15754,6 +16814,12 @@ geo.mapInteractor = function (args) {
spring: {
enabled: false,
springConstant: 0.00005
+ },
+ click: {
+ enabled: true,
+ buttons: {left: true, right: true, middle: true},
+ duration: 0,
+ cancelOnMove: true
}
},
m_options
@@ -15811,6 +16877,19 @@ geo.mapInteractor = function (args) {
// enabled: true | false,
// springConstant: number,
// }
+ //
+ // // enable the "click" event
+ // // A click will be registered when a mouse down is followed
+ // // by a mouse up in less than the given number of milliseconds
+ // // and the standard handler will *not* be called
+ // // If the duration is <= 0, then clicks will only be canceled by
+ // // a mousemove.
+ // click: {
+ // enabled: true | false,
+ // buttons: {'left': true, 'right': true, 'middle': true}
+ // duration: 0,
+ // cancelOnMove: true // cancels click if the mouse is moved before release
+ // }
// }
// A bunch of type definitions for api documentation:
@@ -15987,6 +17066,8 @@ geo.mapInteractor = function (args) {
$node.on('mousedown.geojs', m_this._handleMouseDown);
$node.on('mouseup.geojs', m_this._handleMouseUp);
$node.on('wheel.geojs', m_this._handleMouseWheel);
+ // Disable dragging images and such
+ $node.on('dragstart', function () { return false; });
if (m_options.panMoveButton === 'right' ||
m_options.zoomMoveButton === 'right') {
$node.on('contextmenu.geojs', function () { return false; });
@@ -16197,6 +17278,10 @@ geo.mapInteractor = function (args) {
this._handleMouseDown = function (evt) {
var action = null;
+ if (m_paused) {
+ return;
+ }
+
if (m_state.action === 'momentum') {
// cancel momentum on click
m_state = {};
@@ -16206,6 +17291,17 @@ geo.mapInteractor = function (args) {
m_this._getMouseButton(evt);
m_this._getMouseModifiers(evt);
+ if (m_options.click.enabled &&
+ (!m_mouse.buttons.left || m_options.click.buttons.left) &&
+ (!m_mouse.buttons.right || m_options.click.buttons.right) &&
+ (!m_mouse.buttons.middle || m_options.click.buttons.middle)) {
+ m_clickMaybe = true;
+ if (m_options.click.duration > 0) {
+ window.setTimeout(function () {
+ m_clickMaybe = false;
+ }, m_options.click.duration);
+ }
+ }
if (eventMatch(m_options.panMoveButton, m_options.panMoveModifiers)) {
action = 'pan';
} else if (eventMatch(m_options.zoomMoveButton, m_options.zoomMoveModifiers)) {
@@ -16257,11 +17353,24 @@ geo.mapInteractor = function (args) {
*/
////////////////////////////////////////////////////////////////////////////
this._handleMouseMove = function (evt) {
+
+ if (m_paused) {
+ return;
+ }
+
if (m_state.action) {
// If currently performing a navigation action, the mouse
// coordinates will be captured by the document handler.
return;
}
+
+ if (m_options.click.cancelOnMove) {
+ m_clickMaybe = false;
+ }
+ if (m_clickMaybe) {
+ return;
+ }
+
m_this._getMousePosition(evt);
m_this._getMouseButton(evt);
m_this._getMouseModifiers(evt);
@@ -16275,10 +17384,22 @@ geo.mapInteractor = function (args) {
////////////////////////////////////////////////////////////////////////////
this._handleMouseMoveDocument = function (evt) {
var dx, dy, selectionObj;
+
+ if (m_paused) {
+ return;
+ }
+
m_this._getMousePosition(evt);
m_this._getMouseButton(evt);
m_this._getMouseModifiers(evt);
+ if (m_options.click.cancelOnMove) {
+ m_clickMaybe = false;
+ }
+ if (m_clickMaybe) {
+ return;
+ }
+
if (!m_state.action) {
// This shouldn't happen
console.log('WARNING: Invalid state in mapInteractor.');
@@ -16301,7 +17422,8 @@ geo.mapInteractor = function (args) {
m_this.map().pan({x: dx, y: dy});
} else if (m_state.action === 'zoom') {
m_this.map().zoom(
- m_this.map().zoom() - dy * m_options.zoomScale / 120
+ m_this.map().zoom() - dy * m_options.zoomScale / 120,
+ m_state
);
} else if (m_state.action === 'select') {
// Get the bounds of the current selection
@@ -16400,6 +17522,11 @@ geo.mapInteractor = function (args) {
this._handleMouseUpDocument = function (evt) {
var selectionObj, oldAction;
+ if (m_paused) {
+ return;
+ }
+
+ m_clickMaybe = false;
m_this._getMouseButton(evt);
m_this._getMouseModifiers(evt);
@@ -16437,10 +17564,39 @@ geo.mapInteractor = function (args) {
*/
////////////////////////////////////////////////////////////////////////////
this._handleMouseUp = function (evt) {
+
+ if (m_paused) {
+ return;
+ }
+
+ if (m_clickMaybe) {
+ m_this._handleMouseClick(evt);
+ }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Handle event when a mouse click is detected. A mouse click is a simulated
+ * event that occurs when the time between a mouse down and mouse up
+ * is less than the configured duration and (optionally) if no mousemove
+ * events were triggered in the interim.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._handleMouseClick = function (evt) {
+
m_this._getMouseButton(evt);
m_this._getMouseModifiers(evt);
- // fire a click event here
+ // cancel any ongoing pan action
+ m_this.cancel('pan');
+
+ // unbind temporary handlers on document
+ $(document).off('.geojs');
+
+ // reset click detector variable
+ m_clickMaybe = false;
+
+ // fire a click event
m_this.map().geoTrigger(geo.event.mouseclick, m_this.mouse());
};
@@ -16450,7 +17606,11 @@ geo.mapInteractor = function (args) {
*/
////////////////////////////////////////////////////////////////////////////
this._handleMouseWheel = function (evt) {
- var zoomFactor, direction;
+ var zoomFactor;
+
+ if (m_paused) {
+ return;
+ }
// try to normalize deltas using the wheel event standard:
// https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
@@ -16499,11 +17659,10 @@ geo.mapInteractor = function (args) {
eventMatch('wheel', m_options.zoomWheelModifiers)) {
zoomFactor = -evt.deltaY;
- direction = m_mouse.map;
m_this.map().zoom(
m_this.map().zoom() + zoomFactor,
- direction
+ m_mouse
);
}
};
@@ -16629,6 +17788,24 @@ geo.mapInteractor = function (args) {
return $.extend(true, {}, m_state);
};
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get or set the pause state of the interactor, which
+ * ignores all native mouse and keyboard events.
+ *
+ * @param {bool} [value] The pause state to set or undefined to return the
+ * current state.
+ * @returns {bool|this}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.pause = function (value) {
+ if (value === undefined) {
+ return m_paused;
+ }
+ m_paused = !!value;
+ return m_this;
+ };
+
////////////////////////////////////////////////////////////////////////////
/**
* Simulate a DOM mouse event on connected map.
@@ -16915,3331 +18092,4512 @@ geo.clock = function (opts) {
//////////////////////////////////////////////////////////////////////////////
/**
- * Step to the next frame in the animation. Will set the state to stop
- * if the animation has reached the end and there are no more loops.
- * @private
+ * Step to the next frame in the animation. Will set the state to stop
+ * if the animation has reached the end and there are no more loops.
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ this._setNextFrame = function (step) {
+ var next = new Date(m_this.now().valueOf() + step * m_this.step());
+
+ if (next >= m_this.end() || next <= m_this.start()) {
+ if (m_this.loop() <= m_currentLoop) {
+ m_this.state('stop');
+ return;
+ }
+ m_currentLoop += 1;
+ if (step >= 0) {
+ m_this.now(m_this.start());
+ } else {
+ m_this.now(m_this.end());
+ }
+ return;
+ }
+ m_this.now(next);
+ };
+
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Start an animation.
+ * @param {integer} step The animation frame step (+1 for forward -1 for
+ * reverse, etc).
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ this._animate = function (step) {
+ var myAnimation = {};
+ m_currentAnimation = myAnimation;
+
+ function frame() {
+ if (myAnimation !== m_currentAnimation) {
+ // A new animation has started, so kill this one.
+ return;
+ }
+ m_this._setNextFrame(step);
+ if (m_this.state() === 'play') {
+
+ // Queue the next frame
+ if (!m_this.framerate()) {
+ window.requestAnimationFrame(frame);
+ } else {
+ window.setTimeout(frame, 1000 / m_this.framerate());
+ }
+ } else if (m_this._attached()) {
+ m_this.object().geoTrigger(geo.event.clock[m_this.state()], {
+ current: m_this.now(),
+ clock: m_this
+ });
+ }
+ }
+
+ // trigger the play event
+ if (m_this._attached()) {
+ m_this.object().geoTrigger(geo.event.clock.play, {
+ current: m_this.now(),
+ clock: m_this
+ });
+ }
+
+ // Queue the first frame
+ if (!m_this.framerate()) {
+ window.requestAnimationFrame(frame);
+ } else {
+ window.setTimeout(frame, 1000 / m_this.framerate());
+ }
+ };
+};
+inherit(geo.clock, geo.object);
+
+(function () {
+ 'use strict';
+
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * This class defines the raw interface for a "tile" on a map. A tile is
+ * defined as a rectangular section of a map. The base implementation
+ * is independent of the actual content of the tile, but assumes that
+ * the content is loaded asynchronously via a url. The tile object
+ * has a promise-like interface. For example,
+ *
+ * tile.then(function (data) {...}).catch(function (data) {...});
+ *
+ * @class
+ * @param {Object} spec The tile specification object
+ *
+ * @param {Object} spec.index The global position of the tile
+ * @param {Number} spec.index.x The x-coordinate (usually the column number)
+ * @param {Number} spec.index.y The y-coordinate (usually the row number)
+ *
+ * @param {Object} spec.size The size of each tile
+ * @param {Number} spec.size.x Width (usually in pixels)
+ * @param {Number} spec.size.y Height (usually in pixels)
+ *
+ * @param {Object|String} spec.url A url or jQuery ajax config object
+ *
+ * @param {Object?} spec.overlap The size of overlap with neighboring tiles
+ * @param {Number} [spec.overlap.x=0]
+ * @param {Number} [spec.overlap.y=0]
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ geo.tile = function (spec) {
+ if (!(this instanceof geo.tile)) {
+ return new geo.tile(spec);
+ }
+
+ this._index = spec.index;
+ this._size = spec.size;
+ this._overlap = spec.overlap || {x: 0, y: 0};
+ this._wrap = spec.wrap || {x: 1, y: 1};
+ this._url = spec.url;
+ this._fetched = false;
+
+ /**
+ * Return the index coordinates.
+ */
+ Object.defineProperty(this, 'index', {
+ get:
+ function () { return this._index; }
+ });
+
+ /**
+ * Return the tile sizes.
+ */
+ Object.defineProperty(this, 'size', {
+ get: function () { return this._size; }
+ });
+
+ /**
+ * Return the tile overlap sizes.
+ */
+ Object.defineProperty(this, 'overlap', {
+ get: function () { return this._overlap; }
+ });
+
+ /**
+ * Initiate the ajax request and add a promise interface
+ * to the tile object. This method exists to allow
+ * derived classes the ability to override how the tile
+ * is obtained. For example, imageTile uses an Image
+ * element rather than $.get.
+ */
+ this.fetch = function () {
+ if (!this._fetched) {
+ $.get(this._url).promise(this);
+ }
+ return this;
+ };
+
+ /**
+ * Add a method to be called with the data when the ajax request is
+ * successfully resolved.
+ *
+ * @param {function?} onSuccess The success handler
+ * @param {function?} onFailure The failure handler
+ * @returns {this} Supports chained calling
+ *
+ */
+ this.then = function (onSuccess, onFailure) {
+ this.fetch(); // This will replace the current then method
+
+ // Call then on the new promise
+ this.then(onSuccess, onFailure);
+ return this;
+ };
+
+ /**
+ * Add a method to be called with the data when the ajax fails.
+ *
+ * @param {function} method The rejection handler
+ * @returns {this} Supports chained calling
+ *
+ */
+ this.catch = function (method) {
+ this.then(undefined, method);
+ return this;
+ };
+
+ /**
+ * Return a unique string representation of the given tile useable
+ * as a hash key. Possibly extend later to include url information
+ * to make caches aware of the tile source.
+ * @returns {string}
+ */
+ this.toString = function () {
+ return [this._index.level || 0, this._index.y, this._index.x].join('_');
+ };
+
+ /**
+ * Return the bounds of the tile given an index offset and
+ * a translation.
+ *
+ * @param {object} index The tile index containing (0, 0)
+ * @param {object} shift The coordinates of (0, 0) inside the tile
+ */
+ this.bounds = function (index, shift) {
+ var left, right, bottom, top;
+ left = this.size.x * (this.index.x - index.x) - this.overlap.x - shift.x;
+ right = left + this.size.x + this.overlap.x * 2;
+ top = this.size.y * (this.index.y - index.y) - this.overlap.y - shift.y;
+ bottom = top + this.size.y + this.overlap.y * 2;
+ return {
+ left: left,
+ right: right,
+ bottom: bottom,
+ top: top
+ };
+ };
+
+ /**
+ * Computes the global coordinates of the bottom edge.
+ * @returns {number}
+ */
+ Object.defineProperty(this, 'bottom', {
+ get: function () {
+ return this.size.y * (this.index.y + 1) + this.overlap.y;
+ }
+ });
+
+ /**
+ * Computes the global coordinates of the top edge.
+ * @returns {number}
+ */
+ Object.defineProperty(this, 'top', {
+ get: function () {
+ return this.size.y * this.index.y - this.overlap.y;
+ }
+ });
+
+ /**
+ * Computes the global coordinates of the left edge.
+ * @returns {number}
+ */
+ Object.defineProperty(this, 'left', {
+ get: function () {
+ return this.size.x * this.index.x - this.overlap.x;
+ }
+ });
+
+ /**
+ * Computes the global coordinates of the right edge.
+ * @returns {number}
+ */
+ Object.defineProperty(this, 'right', {
+ get: function () {
+ return this.size.x * (this.index.x + 1) + this.overlap.x;
+ }
+ });
+
+ /**
+ * Returns the global image size at this level.
+ * @returns {number}
+ */
+ Object.defineProperty(this, 'levelSize', {
+ value: {
+ width: Math.pow(2, this.index.level || 0) * this.size.x,
+ height: Math.pow(2, this.index.level || 0) * this.size.y
+ }
+ });
+
+ /**
+ * Set the opacity of the tile to 0 and gradually fade in
+ * over the given number of milliseconds. This will also
+ * resolve the embedded promise interface.
+ * @param {number} duration the duration of the animation in ms
+ * @returns {this} chainable
+ */
+ this.fadeIn = function (duration) {
+ $.noop(duration);
+ return this;
+ };
+ };
+})();
+
+(function () {
+ 'use strict';
+
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * This class defines a tile that is part of a standard "image pyramid", such
+ * as an open street map tile set. Every tile is uniquely indexed by a row,
+ * column, and zoom level. The number of rows/columns at zoom level z is
+ * `2^z`, the number of pixels per tile is configurable.
+ *
+ * By default, this class assumes that images are fetch from the url, but
+ * subclasses may define additional rendering steps to produce the images
+ * before passing them off to the handlers.
+ *
+ * @class
+ * @param {Object} spec The tile specification object
+ *
+ * @param {Object} spec.index The global position of the tile
+ * @param {Number} spec.index.x The x-coordinate (usually the column number)
+ * @param {Number} spec.index.y The y-coordinate (usually the row number)
+ * @param {Number} spec.index.level The zoom level
+ *
+ * @param {Object?} spec.size The size of each tile
+ * @param {Number} [spec.size.x=256] Width in pixels
+ * @param {Number} [spec.size.y=256] Height in pixels
+ *
+ * @param {String} spec.url A url to the image
+ * @param {String} [spec.crossDomain='anonymous'] Image CORS attribute
+ *
+ * @param {Object} spec.overlap The size of overlap with neighboring tiles
+ * @param {Number} [spec.overlap.x=0]
+ * @param {Number} [spec.overlap.y=0]
*/
//////////////////////////////////////////////////////////////////////////////
- this._setNextFrame = function (step) {
- var next = new Date(m_this.now().valueOf() + step * m_this.step());
+ geo.imageTile = function (spec) {
+ if (!(this instanceof geo.imageTile)) {
+ return new geo.imageTile(spec);
+ }
- if (next >= m_this.end() || next <= m_this.start()) {
- if (m_this.loop() <= m_currentLoop) {
- m_this.state('stop');
- return;
- }
- m_currentLoop += 1;
- if (step >= 0) {
- m_this.now(m_this.start());
- } else {
- m_this.now(m_this.end());
+ spec.size = spec.size || {x: 256, y: 256};
+ this._image = null;
+
+ // Cache the coordinate scaling
+ this._cors = spec.crossDomain || 'anonymous';
+
+ // Call superclass constructor
+ geo.tile.call(this, spec);
+
+ /**
+ * Read only accessor to the Image object used by the
+ * tile. Note, this method does not gaurantee that the
+ * image data is available. Use the promise interface
+ * to add asyncronous handlers.
+ * @returns {Image}
+ */
+ Object.defineProperty(this, 'image', {
+ get: function () { return this._image; }
+ });
+
+ /**
+ * Initiate the image request.
+ */
+ this.fetch = function () {
+ var defer;
+ if (!this._image) {
+ this._image = new Image(this.size.x, this.size.y);
+ // Only set the crossOrigin parameter if this is going across origins.
+ if (this._url.indexOf(':') >= 0 && this._url.indexOf('/') >= 0 &&
+ this._url.indexOf(':') < this._url.indexOf('/')) {
+ this._image.crossOrigin = this._cors;
+ }
+ defer = new $.Deferred();
+ this._image.onload = function () { defer.resolve(); };
+ this._image.onerror = function () { defer.reject(); };
+ this._image.src = this._url;
+
+ // attach a promise interface to `this`
+ defer.promise(this);
}
- return;
- }
- m_this.now(next);
+ return this;
+ };
+
+ /**
+ * Set the opacity of the tile to 0 and gradually fade in
+ * over the given number of milliseconds. This will also
+ * resolve the embedded promise interface.
+ * @param {number} duration the duration of the animation in ms
+ * @returns {this} chainable
+ */
+ this.fadeIn = function (duration) {
+ var promise = this.fetch(), defer = new $.Deferred();
+ $(this._image).css('display', 'none');
+ promise.then(function () {
+ $(this._image).fadeIn(duration, function () {
+ defer.resolve();
+ });
+ }.bind(this));
+ return defer.promise(this);
+ };
+
+ return this;
};
+ inherit(geo.imageTile, geo.tile);
+})();
+
+(function () {
+ 'use strict';
+
//////////////////////////////////////////////////////////////////////////////
/**
- * Start an animation.
- * @param {integer} step The animation frame step (+1 for forward -1 for
- * reverse, etc).
- * @private
+ * This class implements a simple cache for tile objects. Each tile is
+ * stored in cache object keyed by a configurable hashing function. Another
+ * array keeps track of last access times for each tile to purge old tiles
+ * once the maximum cache size is reached.
+ *
+ * @class
+ *
+ * @param {Object?} [options] A configuratoin object for the cache
+ * @param {Number} [options.size=64] The maximum number of tiles to store
*/
//////////////////////////////////////////////////////////////////////////////
- this._animate = function (step) {
- var myAnimation = {};
- m_currentAnimation = myAnimation;
+ geo.tileCache = function (options) {
+ if (!(this instanceof geo.tileCache)) {
+ return new geo.tileCache(options);
+ }
+ options = options || {};
+ this._size = options.size || 64;
- function frame() {
- if (myAnimation !== m_currentAnimation) {
- // A new animation has started, so kill this one.
- return;
+ /**
+ * Get/set the maximum cache size.
+ */
+ Object.defineProperty(this, 'size', {
+ get: function () { return this._size; },
+ set: function (n) {
+ while (this._atime.length > n) {
+ this.remove(this._atime[this._atime.length - 1]);
+ }
+ this._size = n;
}
- m_this._setNextFrame(step);
- if (m_this.state() === 'play') {
+ });
- // Queue the next frame
- if (!m_this.framerate()) {
- window.requestAnimationFrame(frame);
- } else {
- window.setTimeout(frame, 1000 / m_this.framerate());
- }
- } else if (m_this._attached()) {
- m_this.object().geoTrigger(geo.event.clock[m_this.state()], {
- current: m_this.now(),
- clock: m_this
- });
+ /**
+ * Get the current cache size.
+ */
+ Object.defineProperty(this, 'length', {
+ get: function () { return this._atime.length; }
+ });
+
+ /**
+ * Get the position of the tile in the access queue.
+ * @param {string} hash The tile's hash value
+ * @returns {number} The position in the queue or -1
+ */
+ this._access = function (hash) {
+ return this._atime.indexOf(hash);
+ };
+
+ /**
+ * Remove a tile from the cache.
+ * @param {string|geo.tile} tile The tile or its hash
+ * @returns {bool} true if a tile was removed
+ */
+ this.remove = function (tile) {
+ var hash = typeof tile === 'string' ? tile : tile.toString();
+
+ // if the tile is not in the cache
+ if (!(hash in this._cache)) {
+ return false;
}
- }
- // trigger the play event
- if (m_this._attached()) {
- m_this.object().geoTrigger(geo.event.clock.play, {
- current: m_this.now(),
- clock: m_this
- });
- }
+ // Remove the tile from the access queue
+ this._atime.splice(this._access(hash), 1);
- // Queue the first frame
- if (!m_this.framerate()) {
- window.requestAnimationFrame(frame);
- } else {
- window.setTimeout(frame, 1000 / m_this.framerate());
- }
- };
-};
-inherit(geo.clock, geo.object);
+ // Remove the tile from the cache
+ delete this._cache[hash];
+ return true;
+ };
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class fileReader
- *
- * @class
- * @extends geo.object
- * @returns {geo.fileReader}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.fileReader = function (arg) {
- "use strict";
- if (!(this instanceof geo.fileReader)) {
- return new geo.fileReader(arg);
- }
- geo.object.call(this);
+ /**
+ * Remove all tiles from the cache.
+ */
+ this.clear = function () {
+ this._cache = {}; // The hash -> tile mapping
+ this._atime = []; // The access queue (the hashes are stored)
+ return this;
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- arg = arg || {};
+ /**
+ * Get a tile from the cache if it exists, otherwise
+ * return null. This method also moves the tile to the
+ * front of the access queue.
+ *
+ * @param {string|geo.tile} hash The tile or the tile hash value
+ * @returns {geo.tile|null}
+ */
+ this.get = function (hash) {
+ hash = typeof hash === 'string' ? hash : hash.toString();
+ if (!(hash in this._cache)) {
+ return null;
+ }
- if (!(arg.layer instanceof geo.featureLayer)) {
- throw "fileReader must be given a feature layer";
- }
+ this._atime.splice(this._access(hash), 1);
+ this._atime.unshift(hash);
+ return this._cache[hash];
+ };
- var m_layer = arg.layer;
+ /**
+ * Add a tile to the cache.
+ * @param {geo.tile} tile
+ */
+ this.add = function (tile) {
+ // remove any existing tiles with the same hash
+ this.remove(tile);
+ var hash = tile.toString();
+
+ // add the tile
+ this._cache[hash] = tile;
+ this._atime.unshift(hash);
+
+ // purge a tile from the cache if necessary
+ while (this._atime.length > this.size) {
+ hash = this._atime.pop();
+ delete this._cache[hash];
+ }
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get the feature layer attached to the reader
- */
- ////////////////////////////////////////////////////////////////////////////
- this.layer = function () {
- return m_layer;
+ this.clear();
+ return this;
};
+})();
+
+(function () {
+ 'use strict';
- ////////////////////////////////////////////////////////////////////////////
/**
- * Tells the caller if it can handle the given file by returning a boolean.
+ * Standard modulo operator where the output is in [0, b) for all inputs.
+ * @private
*/
- ////////////////////////////////////////////////////////////////////////////
- this.canRead = function () {
- return false;
- };
+ function modulo(a, b) {
+ return ((a % b) + b) % b;
+ }
- ////////////////////////////////////////////////////////////////////////////
/**
- * Reads the file object and calls the done function when finished. As an
- * argument to done, provides a boolean that reports if the read was a
- * success. Possibly, it can call done with an object containing details
- * of the read operation.
+ * Pick a subdomain from a list of subdomains based on a the tile location.
+ *
+ * @param {number} x: the x tile coordinate.
+ * @param {number} y: the y tile coordinate.
+ * @param {list} subdomains: the list of known subdomains.
*/
- ////////////////////////////////////////////////////////////////////////////
- this.read = function (file, done) {
- done(false);
- };
+ function m_getTileSubdomain(x, y, subdomains) {
+ return subdomains[modulo(x + y, subdomains.length)];
+ }
- ////////////////////////////////////////////////////////////////////////////
/**
- * Return a FileReader with handlers attached.
- */
- ////////////////////////////////////////////////////////////////////////////
- function newFileReader(done, progress) {
- var reader = new FileReader();
- if (progress) {
- reader.onprogress = progress;
- }
- reader.onloadend = function () {
- if (!reader.result) {
- done(reader.error);
- }
- done(reader.result);
+ * Returns an OSM tile server formatting function from a standard format
+ * string. Replaces {s}, {z}, {x}, and {y}.
+ *
+ * @param {string} base The tile format string
+ * @returns: a conversion function.
+ * @private.
+ */
+ function m_tileUrlFromTemplate(base) {
+ return function (x, y, z, subdomains) {
+ return base.replace('{s}', m_getTileSubdomain(x, y, subdomains))
+ .replace('{z}', z)
+ .replace('{x}', x)
+ .replace('{y}', y);
};
- return reader;
}
- ////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
/**
- * Private method for reading a file object as a string. Calls done with
- * the string content when finished or an error object if unsuccessful.
- * Optionally, the caller can provide a progress method that is called
- * after reading each slice.
+ * This method defines a tileLayer, which is an abstract class defining a
+ * layer divided into tiles of arbitrary data. Notably, this class provides
+ * the core functionality of the osmLayer, but hooks exist to render tiles
+ * dynamically from vector data, or to display arbitrary grids of images
+ * in a custom coordinate system. When multiple zoom levels are present
+ * in a given dataset, this class assumes that the space occupied by
+ * tile (i, j) at level z is covered by a 2x2 grid of tiles at zoom
+ * level z + 1:
+ *
+ * (2i, 2j), (2i, 2j + 1)
+ * (2i + 1, 2j), (2i + 1, 2j + 1)
+ *
+ * The higher level tile set should represent a 2x increase in resolution.
+ *
+ * Although not currently supported, this class is intended to extend to
+ * 3D grids of tiles as well where 1 tile is covered by a 2x2x2 grid of
+ * tiles at the next level. The tiles are assumed to be rectangular,
+ * identically sized, and aligned with x/y axis of the underlying
+ * coordinate system. The local coordinate system is in pixels relative
+ * to the current zoom level and changes when the zoom level crosses an
+ * integer threshold.
+ *
+ * The constructor takes a number of optional parameters that customize
+ * the display of the tiles. The default values of these options are
+ * stored as the `defaults` attribution on the constructor. Supporting
+ * alternate tiling protocols often only requires adjusting these
+ * defaults.
+ *
+ * @class
+ * @extends geo.featureLayer
+ * @param {object?} options
+ * @param {number} [options.minLevel=0] The minimum zoom level available
+ * @param {number} [options.maxLevel=18] The maximum zoom level available
+ * @param {number} [options.tileOverlap=0]
+ * Number of pixels of overlap between tiles
+ * @param {number} [options.tileWidth=256]
+ * The tile width as displayed without overlap
+ * @param {number} [options.tileHeight=256]
+ * The tile height as displayed without overlap
+ * @param {number} [options.cacheSize=400] The maximum number of tiles to
+ * cache. The default is 200 if keepLower is false.
+ * @param {bool} [options.keepLower=true]
+ * Keep lower zoom level tiles when showing high zoom level tiles. This
+ * uses more memory but results in smoother transitions.
+ * @param {bool} [options.wrapX=true] Wrap in the x-direction
+ * @param {bool} [options.wrapY=false] Wrap in the y-direction
+ * @param {number} [options.minX=0] The minimum world coordinate in X
+ * @param {number} [options.maxX=255] The maximum world coordinate in X
+ * @param {number} [options.minY=0] The minimum world coordinate in Y
+ * @param {number} [options.maxY=255] The maximum world coordinate in Y
+ * @param {function|string} [options.url=null]
+ * A function taking the current tile indices and returning a URL or jquery
+ * ajax config to be passed to the {geo.tile} constructor.
+ * Example:
+ * (x, y, z, subdomains) => "http://example.com/z/y/x.png"
+ * If this is a string, a template url with {x}, {y}, {z}, and {s} as
+ * template variables. {s} picks one of the subdomains parameter.
+ * @param {string|list} [options.subdomain="abc"] Subdomains to use in
+ * template url strings. If a string, this is converted to a list before
+ * being passed to a url function.
+ * @param {string} [options.baseUrl=null] If defined, use the old-style base
+ * url instead of the options.url parameter. This is functionally the same
+ * as using a url of baseUrl/{z}/{x}/{y}.(options.imageFormat || png). If
+ * the specified string does not end in a slash, one is added.
+ * @param {string} [options.imageFormat='png']
+ * This is only used if a baseUrl is specified, in which case it determines
+ * the image name extension used in the url.
+ * @param {number} [options.animationDuration=0]
+ * The number of milliseconds for the tile loading animation to occur. **This
+ * option is currently buggy because old tiles will purge before the animation
+ * is complete.**
+ * @param {string} [options.attribution]
+ * An attribution to display with the layer (accepts HTML)
+ * @param {function} [options.tileRounding=Math.round]
+ * This function determines which tiles will be loaded when the map is at
+ * a non-integer zoom. For example, `Math.floor`, will use tile level 2
+ * when the map is at zoom 2.9.
+ * @param {function} [options.tileOffset]
+ * This function takes a zoom level argument and returns, in units of
+ * pixels, the coordinates of the point (0, 0) at the given zoom level
+ * relative to the bottom left corner of the domain.
+ * @param {bool} [options.topDown=false] True if the gcs is top-down,
+ * false if bottom-up (the ingcs does not matter, only the gcs coordinate
+ * system). When false, this inverts the gcs y-coordinate when calculating
+ * local coordinates.
+ * @returns {geo.tileLayer}
*/
- ////////////////////////////////////////////////////////////////////////////
- this._getString = function (file, done, progress) {
- var reader = newFileReader(done, progress);
- reader.readAsText(file);
- };
+ //////////////////////////////////////////////////////////////////////////////
+ geo.tileLayer = function (options) {
+ if (!(this instanceof geo.tileLayer)) {
+ return new geo.tileLayer(options);
+ }
+ geo.featureLayer.call(this, options);
+
+ options = $.extend(true, {}, this.constructor.defaults, options || {});
+ if (!options.cacheSize) {
+ // this size should be sufficient for a 4k display
+ options.cacheSize = options.keepLower ? 400 : 200;
+ }
+ if ($.type(options.subdomains) === 'string') {
+ options.subdomains = options.subdomains.split('');
+ }
+ /* We used to call the url option baseUrl. If a baseUrl is specified, use
+ * it instead of url, interpretting it as before. */
+ if (options.baseUrl) {
+ var url = options.baseUrl;
+ if (url && url.charAt(url.length - 1) !== '/') {
+ url += '/';
+ }
+ options.url = url + '{z}/{x}/{y}.' + (options.imageFormat || 'png');
+ }
+ /* Save the original url so that we can return it if asked */
+ options.originalUrl = options.url;
+ if ($.type(options.url) === 'string') {
+ options.url = m_tileUrlFromTemplate(options.url);
+ }
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Like _getString, but returns an ArrayBuffer object.
- */
- ////////////////////////////////////////////////////////////////////////////
- this._getArrayBuffer = function (file, done, progress) {
- var reader = newFileReader(done, progress);
- reader.readAsText(file);
- };
+ var lastZoom = null, lastX = null, lastY = null, s_init = this._init,
+ _deferredPurge = null;
- return this;
-};
+ // copy the options into a private variable
+ this._options = $.extend(true, {}, options);
-inherit(geo.fileReader, geo.object);
+ // set the layer attribution text
+ this.attribution(options.attribution);
-/*global File*/
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class jsonReader
- *
- * @class
- * @extends geo.fileReader
- * @returns {geo.jsonReader}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.jsonReader = function (arg) {
- 'use strict';
- if (!(this instanceof geo.jsonReader)) {
- return new geo.jsonReader(arg);
- }
+ // initialize the object that keeps track of actively drawn tiles
+ this._activeTiles = {};
- var m_this = this, m_style = arg.style || {};
- m_style = $.extend({
- 'strokeWidth': 2,
- 'strokeColor': {r: 0, g: 0, b: 0},
- 'strokeOpacity': 1,
- 'fillColor': {r: 1, g: 0, b: 0},
- 'fillOpacity': 1
- }, m_style);
+ // initialize the object that stores active tile regions in a
+ // tree-like structure providing quick queries to determine
+ // if tiles are completely obscured or not.
+ this._tileTree = {};
+
+ // initialize the in memory tile cache
+ this._cache = geo.tileCache({size: options.cacheSize});
+
+ /**
+ * Readonly accessor to the options object
+ */
+ Object.defineProperty(this, 'options', {get: function () {
+ return $.extend({}, this._options);
+ }});
+
+ /**
+ * Readonly accessor to the tile cache object.
+ */
+ Object.defineProperty(this, 'cache', {get: function () {
+ return this._cache;
+ }});
- geo.fileReader.call(this, arg);
+ /**
+ * Readonly accessor to the active tile mapping. This is an object containing
+ * all currently drawn tiles (hash(tile) => tile).
+ */
+ Object.defineProperty(this, 'activeTiles', {get: function () {
+ return $.extend({}, this._activeTiles); // copy on output
+ }});
- this.canRead = function (file) {
- if (file instanceof File) {
- return (file.type === 'application/json' || file.name.match(/\.json$/));
- } else if (typeof file === 'string') {
- try {
- JSON.parse(file);
- } catch (e) {
+ /**
+ * The number of tiles at the given zoom level
+ * The default implementation just returns `Math.pow(2, z)`.
+ * @param {number} level A zoom level
+ * @returns {{x: nx, y: ny}} The number of tiles in each axis
+ */
+ this.tilesAtZoom = function (level) {
+ var s = Math.pow(2, level);
+ return {x: s, y: s};
+ };
+
+ /**
+ * Returns true if the given tile index is valid:
+ * * min level <= level <= max level
+ * * 0 <= x <= 2^level - 1
+ * * 0 <= y <= 2^level - 1
+ * @param {object} index The tile index
+ * @param {number} index.x
+ * @param {number} index.y
+ * @param {number} index.level
+ * @returns {geo.tile}
+ */
+ this.isValid = function (index) {
+ if (!(this._options.minLevel <= index.level &&
+ index.level <= this._options.maxLevel)) {
+ return false;
+ }
+ if (!(this._options.wrapX ||
+ 0 <= index.x &&
+ index.x <= this.tilesAtZoom(index.level).x - 1)) {
+ return false;
+ }
+ if (!(this._options.wrapY ||
+ 0 <= index.y &&
+ index.y <= this.tilesAtZoom(index.level).y - 1)) {
return false;
}
return true;
- }
- try {
- if (Array.isArray(m_this._featureArray(file))) {
- return true;
+ };
+
+ /**
+ * Returns the current origin tile and offset at the given zoom level.
+ * This is intended to be cached in the future to optimize coordinate
+ * transformations.
+ * @protected
+ * @param {number} level The target zoom level
+ * @returns {object} {index: {x, y}, offset: {x, y}}
+ */
+ this._origin = function (level) {
+ var origin = this.toLevel(this.toLocal(this.map().origin()), level),
+ o = this._options,
+ index, offset;
+
+ // get the tile index
+ index = {
+ x: Math.floor(origin.x / o.tileWidth),
+ y: Math.floor(origin.y / o.tileHeight)
+ };
+
+ // get the offset inside the tile (in pixels)
+ // This computation should contain the only numerically unstable
+ // subtraction in this class. All other methods will assume
+ // coordinates are given relative to the map origin.
+ offset = {
+ x: origin.x - o.tileWidth * index.x,
+ y: origin.y - o.tileHeight * index.y
+ };
+ return {index: index, offset: offset};
+ };
+
+ /**
+ * Returns a tile's bounds in its level coordinates.
+ * @param {geo.tile} tile
+ * @returns {object} bounds
+ */
+ this._tileBounds = function (tile) {
+ var origin = this._origin(tile.index.level);
+ return tile.bounds(origin.index, origin.offset);
+ };
+
+ /**
+ * Returns the tile indices at the given point.
+ * @param {object} point The coordinates in pixels relative to the map origin.
+ * @param {number} point.x
+ * @param {number} point.y
+ * @param {number} level The target zoom level
+ * @returns {object} The tile indices
+ */
+ this.tileAtPoint = function (point, level) {
+ var o = this._origin(level);
+ var map = this.map();
+ point = this.displayToLevel(map.gcsToDisplay(point, null), level);
+ var to = this._options.tileOffset(level);
+ if (to) {
+ point.x += to.x;
+ point.y += to.y;
}
- } catch (e) {}
- return false;
- };
+ var tile = {
+ x: Math.floor(
+ o.index.x + (o.offset.x + point.x) / this._options.tileWidth
+ ),
+ y: Math.floor(
+ o.index.y + (o.offset.y + point.y) / this._options.tileHeight
+ )
+ };
+ return tile;
+ };
- this._readObject = function (file, done, progress) {
- var object;
- function onDone(fileString) {
- if (typeof fileString !== 'string') {
- done(false);
+ /**
+ * Returns a tile's bounds in a gcs.
+ * @param {object|tile} either a tile or an object with {x, y, level}
+ * specifying a tile.
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @returns {object} The tile bounds in the specified gcs.
+ */
+ this.gcsTileBounds = function (indexOrTile, gcs) {
+ var tile = (indexOrTile.index ? indexOrTile : geo.tile({
+ index: indexOrTile,
+ size: {x: this._options.tileWidth, y: this._options.tileHeight},
+ url: ''
+ }));
+ var to = this._options.tileOffset(tile.index.level),
+ bounds = tile.bounds({x: 0, y: 0}, to),
+ map = this.map(),
+ unit = map.unitsPerPixel(tile.index.level);
+ var coord = [{
+ x: bounds.left * unit, y: this._topDown() * bounds.top * unit
+ }, {
+ x: bounds.right * unit, y: this._topDown() * bounds.bottom * unit
+ }];
+ gcs = (gcs === null ? map.gcs() : (
+ gcs === undefined ? map.ingcs() : gcs));
+ if (gcs !== map.gcs()) {
+ coord = geo.transform.transformCoordinates(gcs, map.gcs(), coord);
}
+ return {
+ left: coord[0].x,
+ top: coord[0].y,
+ right: coord[1].x,
+ bottom: coord[1].y
+ };
+ };
- // We have two possibilities here:
- // 1) fileString is a JSON string or is
- // a URL.
- try {
- object = JSON.parse(fileString);
- done(object);
- } catch (e) {
- if (!object) {
- $.ajax({
- type: 'GET',
- url: fileString,
- dataType: 'text'
- }).done(function (data) {
- object = JSON.parse(data);
- done(object);
- }).fail(function () {
- done(false);
- });
+ /**
+ * Returns an instantiated tile object with the given indices. This
+ * method always returns a new tile object. Use `_getTileCached`
+ * to use the caching layer.
+ * @param {object} index The tile index
+ * @param {number} index.x
+ * @param {number} index.y
+ * @param {number} index.level
+ * @param {object} source The tile index used for constructing the url
+ * @param {number} source.x
+ * @param {number} source.y
+ * @param {number} source.level
+ * @returns {geo.tile}
+ */
+ this._getTile = function (index, source) {
+ var urlParams = source || index;
+ return geo.tile({
+ index: index,
+ size: {x: this._options.tileWidth, y: this._options.tileHeight},
+ url: this._options.url(urlParams.x, urlParams.y, urlParams.level || 0,
+ this._options.subdomains)
+ });
+ };
+
+ /**
+ * Returns an instantiated tile object with the given indices. This
+ * method is similar to `_getTile`, but checks the cache before
+ * generating a new tile.
+ * @param {object} index The tile index
+ * @param {number} index.x
+ * @param {number} index.y
+ * @param {number} index.level
+ * @param {object} source The tile index used for constructing the url
+ * @param {number} source.x
+ * @param {number} source.y
+ * @param {number} source.level
+ * @returns {geo.tile}
+ */
+ this._getTileCached = function (index, source) {
+ var tile = this.cache.get(this._tileHash(index));
+ if (tile === null) {
+ tile = this._getTile(index, source);
+ this.cache.add(tile);
+ }
+ return tile;
+ };
+
+ /**
+ * Returns a string representation of the tile at the given index.
+ * This method is used as a hashing function for the caching layer.
+ *
+ * Note: This method _must_ return the same string as:
+ *
+ * tile({index: index}).toString();
+ *
+ * @param {object} index The tile index
+ * @param {number} index.x
+ * @param {number} index.y
+ * @param {number} index.level
+ * @returns {string}
+ */
+ this._tileHash = function (index) {
+ return [index.level || 0, index.y, index.x].join('_');
+ };
+
+ /**
+ * Returns the optimal starting and ending tile indices
+ * (inclusive) necessary to fill the given viewport.
+ * @param {number} level The zoom level
+ * @param {object} bounds The map bounds in world coordinates
+ */
+ this._getTileRange = function (level, bounds) {
+ return {
+ start: this.tileAtPoint({
+ x: bounds.left,
+ y: bounds.top
+ }, level),
+ end: this.tileAtPoint({
+ x: bounds.right,
+ y: bounds.bottom
+ }, level)
+ };
+ };
+
+ /**
+ * Returns a list of tiles necessary to fill the screen at the given
+ * zoom level, center point, and viewport size. The list is optionally
+ * ordered by loading priority (center tiles first).
+ *
+ * @protected
+ * @param {number} maxLevel The zoom level
+ * @param {object} bounds The map bounds
+ * @param {boolean} sorted Return a sorted list
+ * @returns {geo.tile[]} An array of tile objects
+ */
+ this._getTiles = function (maxLevel, bounds, sorted) {
+ var i, j, tiles = [], index, nTilesLevel,
+ start, end, indexRange, source, center,
+ level, minLevel = this._options.keepLower ? 0 : maxLevel;
+
+ for (level = minLevel; level <= maxLevel; level += 1) {
+ // get the tile range to fetch
+ indexRange = this._getTileRange(level, bounds);
+ start = indexRange.start;
+ end = indexRange.end;
+
+ // total number of tiles existing at this level
+ nTilesLevel = this.tilesAtZoom(level);
+
+ // loop over the tile range
+ index = {level: level};
+ index.nx = nTilesLevel.x;
+ index.ny = nTilesLevel.y;
+
+ for (i = start.x; i <= end.x; i += 1) {
+ index.x = i;
+ for (j = start.y; j <= end.y; j += 1) {
+ index.y = j;
+
+ source = $.extend({}, index);
+ if (this._options.wrapX) {
+ source.x = modulo(index.x, index.nx);
+ }
+ if (this._options.wrapY) {
+ source.y = modulo(index.y, index.ny);
+ }
+
+ if (this.isValid(source)) {
+ tiles.push(this._getTileCached($.extend({}, index), source));
+ }
+ }
}
}
- }
- if (file instanceof File) {
- m_this._getString(file, onDone, progress);
- } else if (typeof file === 'string') {
- onDone(file);
- } else {
- done(file);
- }
- };
+ if (sorted) {
+ center = {
+ x: (start.x + end.x) / 2,
+ y: (start.y + end.y) / 2
+ };
+ tiles.sort(this._loadMetric(center));
+ }
+ return tiles;
+ };
- this._featureArray = function (spec) {
- if (spec.type === 'FeatureCollection') {
- return spec.features || [];
- }
- if (spec.type === 'GeometryCollection') {
- throw 'GeometryCollection not yet implemented.';
- }
- if (Array.isArray(spec.coordinates)) {
- return spec;
- }
- throw 'Unsupported collection type: ' + spec.type;
- };
+ /**
+ * Prefetches tiles up to a given zoom level around a given bounding box.
+ *
+ * @param {number} level The zoom level
+ * @param {object} bounds The map bounds
+ * @returns {$.Deferred} resolves when all of the tiles are fetched
+ */
+ this.prefetch = function (level, bounds) {
+ var tiles;
+ tiles = this._getTiles(level, bounds, true);
+ return $.when.apply($,
+ tiles.map(function (tile) {
+ return tile.fetch();
+ })
+ );
+ };
- this._featureType = function (spec) {
- var geometry = spec.geometry || {};
- if (geometry.type === 'Point' || geometry.type === 'MultiPoint') {
- return 'point';
- }
- if (geometry.type === 'LineString') {
- return 'line';
- }
- if (geometry.type === 'Polygon') {
- return 'polygon';
- }
- if (geometry.type === 'MultiPolygon') {
- return 'multipolygon';
- }
- return null;
- };
+ /**
+ * This method returns a metric that determines tile loading order. The
+ * default implementation prioritizes tiles that are closer to the center,
+ * or at a lower zoom level.
+ * @protected
+ * @param {index1} center The center tile
+ * @param {number} center.x
+ * @param {number} center.y
+ * @returns {function} A function accepted by Array.prototype.sort
+ */
+ this._loadMetric = function (center) {
+ return function (a, b) {
+ var a0, b0, dx, dy, cx, cy, scale;
- this._getCoordinates = function (spec) {
- var geometry = spec.geometry || {},
- coordinates = geometry.coordinates || [], elv;
+ // shortcut if zoom level differs
+ if (a.level !== b.level) {
+ return b.level - a.level;
+ }
- if ((coordinates.length === 2 || coordinates.length === 3) &&
- (isFinite(coordinates[0]) && isFinite(coordinates[1]))) {
+ // compute the center coordinates relative to a.level
+ scale = Math.pow(2, a.level - center.level);
+ cx = center.x * scale;
+ cy = center.y * scale;
- // Do we have a elevation component
- if (isFinite(coordinates[2])) {
- elv = coordinates[2];
- }
+ // calculate distances to the center squared
+ dx = a.x - cx;
+ dy = a.y - cy;
+ a0 = dx * dx + dy * dy;
- // special handling for single point coordinates
- return [{x: coordinates[0], y: coordinates[1], z: elv}];
- }
+ dx = b.x - cx;
+ dy = b.y - cy;
+ b0 = dx * dx + dy * dy;
- // need better handling here, but we can plot simple polygons
- // by taking just the outer linearring
- if (Array.isArray(coordinates[0][0])) {
- coordinates = coordinates[0];
- }
+ // return negative if a < b, or positive if a > b
+ return a0 - b0;
+ };
+ };
- // return an array of points for LineString, MultiPoint, etc...
- return coordinates.map(function (c) {
+ /**
+ * Convert a coordinate from pixel coordinates at the given zoom
+ * level to world coordinates.
+ * @param {object} coord
+ * @param {number} coord.x The offset in pixels (level 0) from the left edge
+ * @param {number} coord.y The offset in pixels (level 0) from the bottom edge
+ * @param {number} level The zoom level of the source coordinates
+ */
+ this.fromLevel = function (coord, level) {
+ var s = Math.pow(2, -level);
return {
- x: c[0],
- y: c[1],
- z: c[2]
+ x: coord.x * s,
+ y: coord.y * s
};
- });
- };
+ };
- this._getStyle = function (spec) {
- return spec.properties;
- };
+ /**
+ * Convert a coordinate from layer coordinates to pixel coordinates at the
+ * given zoom level.
+ * @param {object} coord
+ * @param {number} coord.x The offset in pixels (level 0) from the left edge
+ * @param {number} coord.y The offset in pixels (level 0) from the bottom edge
+ * @param {number} level The zoom level of the new coordinates
+ */
+ this.toLevel = function (coord, level) {
+ var s = Math.pow(2, level);
+ return {
+ x: coord.x * s,
+ y: coord.y * s
+ };
+ };
- this.read = function (file, done, progress) {
+ /**
+ * Draw the given tile on the active canvas.
+ * @param {geo.tile} tile The tile to draw
+ */
+ this.drawTile = function (tile) {
+ var hash = tile.toString();
- function _done(object) {
- var features, allFeatures = [];
+ if (this._activeTiles.hasOwnProperty(hash)) {
+ // the tile is already drawn, move it to the top
+ this._moveToTop(tile);
+ } else {
+ // pass to the rendering implementation
+ this._drawTile(tile);
+ }
- features = m_this._featureArray(object);
+ // add the tile to the active cache
+ this._activeTiles[hash] = tile;
+ };
- features.forEach(function (feature) {
- var type = m_this._featureType(feature),
- coordinates = m_this._getCoordinates(feature),
- style = m_this._getStyle(feature);
- if (type) {
- if (type === 'line') {
- style.fill = style.fill || false;
- allFeatures.push(m_this._addFeature(
- type,
- [coordinates],
- style,
- feature.properties
- ));
- } else if (type === 'point') {
- style.stroke = style.stroke || false;
- allFeatures.push(m_this._addFeature(
- type,
- coordinates,
- style,
- feature.properties
- ));
- } else if (type === 'polygon') {
- style.fill = style.fill === undefined ? true : style.fill;
- style.fillOpacity = (
- style.fillOpacity === undefined ? 0.25 : style.fillOpacity
- );
- // polygons not yet supported
- allFeatures.push(m_this._addFeature(
- 'line',
- [coordinates],
- style,
- feature.properties
- ));
- } else if (type === 'multipolygon') {
- style.fill = style.fill === undefined ? true : style.fill;
- style.fillOpacity = (
- style.fillOpacity === undefined ? 0.25 : style.fillOpacity
- );
+ /**
+ * Render the tile on the canvas. This implementation draws the tiles directly
+ * on the DOM using
tags. Derived classes should override this method
+ * to draw the tile on a renderer specific context.
+ * @protected
+ * @param {geo.tile} tile The tile to draw
+ */
+ this._drawTile = function (tile) {
+ // Make sure this method is not called when there is
+ // a renderer attached.
+ //
+ if (this.renderer() !== null) {
+ throw new Error('This draw method is not valid on renderer managed layers.');
+ }
- coordinates = feature.geometry.coordinates.map(function (c) {
- return c[0].map(function (el) {
- return {
- x: el[0],
- y: el[1],
- z: el[2]
- };
- });
- });
+ // get the layer node
+ var div = $(this._getSubLayer(tile.index.level)),
+ bounds = this._tileBounds(tile),
+ duration = this._options.animationDuration,
+ container = $('
').attr(
+ 'tile-reference', tile.toString());
+
+ // apply a transform to place the image correctly
+ container.append(tile.image);
+ container.css({
+ 'position': 'absolute',
+ 'left': bounds.left + 'px',
+ 'top': bounds.top + 'px'
+ });
- allFeatures.push(m_this._addFeature(
- 'line',
- coordinates,
- style,
- feature.properties
- ));
- }
+ // apply fade in animation
+ if (duration > 0) {
+ tile.fadeIn(duration);
+ }
+
+ // append the image element
+ div.append(container);
+
+ // add an error handler
+ tile.catch(function () {
+ // May want to do something special here later
+ console.warn('Could not load tile at ' + tile.index);
+ this._remove(tile);
+ }.bind(this));
+ };
+
+ /**
+ * Remove the given tile from the canvas and the active cache.
+ * @param {geo.tile|string} tile The tile (or hash) to remove
+ * @returns {geo.tile} the tile removed from the active layer
+ */
+ this.remove = function (tile) {
+ var hash = tile.toString();
+ var value = this._activeTiles[hash];
+
+ if (value instanceof geo.tile) {
+ this._remove(value);
+ }
+
+ delete this._activeTiles[hash];
+ return value;
+ };
+
+ /**
+ * Remove the given tile from the canvas. This implementation just
+ * finds and removes the
element created for the tile.
+ * @param {geo.tile|string} tile The tile object to remove
+ */
+ this._remove = function (tile) {
+ if (tile.image) {
+ if (tile.image.parentElement) {
+ $(tile.image.parentElement).remove();
} else {
- console.log('unsupported feature type: ' + feature.geometry.type);
+ /* This shouldn't happen, but sometimes does. Originally it happened
+ * when a tile was removed from the cache before it was finished
+ * being used; there is still some much rarer condition that can
+ * cause it. Log that it happened until we can figure out how to fix
+ * the issue. */
+ console.log('No parent element to remove ' + tile.toString(), tile);
}
- });
-
- if (done) {
- done(allFeatures);
+ $(tile.image).remove();
}
- }
-
- m_this._readObject(file, _done, progress);
- };
+ };
+ /**
+ * Move the given tile to the top on the canvas.
+ * @param {geo.tile} tile The tile object to move
+ */
+ this._moveToTop = function (tile) {
+ $.noop(tile);
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Build the data array for a feature given the coordinates and properties
- * from the geojson.
- *
- * @private
- * @param {Object[]} coordinates Coordinate data array
- * @param {Object} properties Geojson properties object
- * @param {Object} style Global style defaults
- * @returns {Object[]}
- */
- //////////////////////////////////////////////////////////////////////////////
- this._buildData = function (coordinates, properties, style) {
- return coordinates.map(function (coord) {
+ /**
+ * Query the attached map for the current bounds and return them
+ * as pixels at the current zoom level.
+ * @returns {object}
+ * Bounds object with left, right, top, bottom keys
+ * @protected
+ */
+ this._getViewBounds = function () {
+ var map = this.map(),
+ mapZoom = map.zoom(),
+ zoom = this._options.tileRounding(mapZoom),
+ scale = Math.pow(2, mapZoom - zoom),
+ size = map.size();
+ var ul = this.displayToLevel({x: 0, y: 0});
+ var lr = this.displayToLevel({x: size.width, y: size.height});
return {
- coordinates: coord,
- properties: properties,
- style: style
+ level: zoom,
+ scale: scale,
+ left: ul.x,
+ right: lr.x,
+ bottom: lr.y,
+ top: ul.y
};
- });
- };
-
- this._addFeature = function (type, coordinates, style, properties) {
- var _style = $.extend({}, m_style, style);
- var feature = m_this.layer().createFeature(type)
- .data(m_this._buildData(coordinates, properties, style))
- .style(_style);
+ };
- if (type === 'line') {
- feature.line(function (d) { return d.coordinates; });
- } else {
- feature.position(function (d) { return d.coordinates; });
- }
- return feature;
- };
+ /**
+ * Remove all inactive tiles from the display. An inactive tile
+ * is one that is no longer visible either because it was panned
+ * out of the active view or the zoom has changed.
+ * @protected
+ * @param {number} zoom Tiles (in bounds) at this zoom level will be kept
+ * @param {boolean} doneLoading If true, allow purging additional tiles.
+ */
+ this._purge = function (zoom, doneLoading) {
+ var tile, hash, bounds = {};
-};
+ // Don't purge tiles in an active update
+ if (this._updating) {
+ return;
+ }
-inherit(geo.jsonReader, geo.fileReader);
+ // get the view bounds
+ bounds = this._getViewBounds();
-geo.registerFileReader('jsonReader', geo.jsonReader);
+ for (hash in this._activeTiles) {// jshint ignore: line
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class map.
- *
- * Creation tags a dictionary of arguments, which can include:
- * center: {x: (center x value), y: (center y value)}
- * gcs:
- * uigcs:
- * node:
- * layers:
- * zoom: (number) - initial zoom level
- * min: (number) - minimum zoom level
- * max: (number) - maximum zoom level
- * width:
- * height:
- * parallelProjection: (bool) - true to use parallel projection, false to use
- * perspective.
- * discreteZoom: (bool) - true to only allow integer zoom levels, false to
- * allow any zoom level.
- * autoResize:
- * clampBounds:
- * interactor:
- * clock:
- *
- * Creates a new map inside of the given HTML layer (Typically DIV)
- * @class
- * @extends geo.sceneObject
- * @returns {geo.map}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.map = function (arg) {
- 'use strict';
- if (!(this instanceof geo.map)) {
- return new geo.map(arg);
- }
- arg = arg || {};
- geo.sceneObject.call(this, arg);
- arg.layers = arg.layers === undefined ? [] : arg.layers;
+ tile = this._activeTiles[hash];
+ if (this._canPurge(tile, bounds, zoom, doneLoading)) {
+ this.remove(tile);
+ }
+ }
+ return this;
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Private member variables
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_exit = this._exit,
- m_x = 0,
- m_y = 0,
- m_node = $(arg.node),
- m_width = arg.width || m_node.width(),
- m_height = arg.height || m_node.height(),
- m_gcs = arg.gcs === undefined ? 'EPSG:4326' : arg.gcs,
- m_uigcs = arg.uigcs === undefined ? 'EPSG:4326' : arg.uigcs,
- m_center = { x: 0, y: 0 },
- m_zoom = arg.zoom === undefined ? 4 : arg.zoom,
- m_baseLayer = null,
- m_fileReader = null,
- m_interactor = null,
- m_validZoomRange = { min: 0, max: 16 },
- m_transition = null,
- m_queuedTransition = null,
- m_clock = null,
- m_parallelProjection = arg.parallelProjection ? true : false,
- m_discreteZoom = arg.discreteZoom ? true : false,
- m_bounds = {};
+ /**
+ * Remove all active tiles from the canvas.
+ * @returns {geo.tile[]} The array of tiles removed
+ */
+ this.clear = function () {
+ var tiles = [], tile;
- arg.center = geo.util.normalizeCoordinates(arg.center);
- arg.autoResize = arg.autoResize === undefined ? true : arg.autoResize;
- arg.clampBounds = arg.clampBounds === undefined ? true : arg.clampBounds;
+ // ignoring the warning here because this is a privately
+ // controlled object with simple keys
+ for (tile in this._activeTiles) { // jshint ignore: line
+ tiles.push(this.remove(tile));
+ }
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get map gcs
- *
- * @returns {string}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.gcs = function (arg) {
- if (arg === undefined) {
- return m_gcs;
- }
- m_gcs = arg;
- return m_this;
- };
+ // clear out the tile coverage tree
+ this._tileTree = {};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get map user interface GCS
- *
- * @returns {string}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.uigcs = function () {
- return m_uigcs;
- };
+ return tiles;
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get root node of the map
- *
- * @returns {object}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.node = function () {
- return m_node;
- };
+ /**
+ * Reset the layer to the initial state, clearing the canvas
+ * and resetting the tile cache.
+ * @returns {this} Chainable
+ */
+ this.reset = function () {
+ this.clear();
+ this._cache.clear();
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set zoom level of the map
- *
- * @returns {Number|geo.map}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.zoom = function (val, direction, ignoreDiscreteZoom) {
- var base, evt, recenter = false;
- if (val === undefined) {
- return m_zoom;
- }
+ /**
+ * Compute local coordinates from the given world coordinates. The
+ * tile layer uses units of pixels relative to the world space
+ * coordinate origin.
+ * @param {object} pt A point in world space coordinates
+ * @param {number|undefined} zoom If unspecified, use the map zoom.
+ * @returns {object} Local coordinates
+ */
+ this.toLocal = function (pt, zoom) {
+ var map = this.map(),
+ unit = map.unitsPerPixel(zoom === undefined ? map.zoom() : zoom);
+ return {
+ x: pt.x / unit,
+ y: this._topDown() * pt.y / unit
+ };
+ };
- /* The ignoreDiscreteZoom flag is intended to allow non-integer zoom values
- * during animation. */
- if (m_discreteZoom && val !== Math.round(val) && !ignoreDiscreteZoom) {
- /* If we are using discrete zoom levels and the value we were given is
- * not an integer, then try to detect if we are enlarging or shrinking
- * and perform the expected behavior. Otherwise, make sure we are at an
- * integer level. We may need to revisit for touch zoom events. */
- if (m_zoom !== Math.round(m_zoom) || Math.abs(val - m_zoom) < 0.01) {
- val = Math.round(m_zoom);
- } else if (val < m_zoom) {
- val = Math.min(Math.round(val), m_zoom - 1);
- } else if (val > m_zoom) {
- val = Math.max(Math.round(val), m_zoom + 1);
- }
- }
- val = Math.min(m_validZoomRange.max, Math.max(val, m_validZoomRange.min));
- if (val === m_zoom) {
- return m_this;
- }
+ /**
+ * Compute world coordinates from the given local coordinates. The
+ * tile layer uses units of pixels relative to the world space
+ * coordinate origin.
+ * @param {object} pt A point in world space coordinates
+ * @param {number|undefined} zoom If unspecified, use the map zoom.
+ * @returns {object} Local coordinates
+ */
+ this.fromLocal = function (pt, zoom) {
+ var map = this.map(),
+ unit = map.unitsPerPixel(zoom === undefined ? map.zoom() : zoom);
+ return {
+ x: pt.x * unit,
+ y: this._topDown() * pt.y * unit
+ };
+ };
- base = m_this.baseLayer();
+ /**
+ * Return a factor for invertin the y units as appropriate.
+ * @return {number}
+ */
+ this._topDown = function () {
+ return this._options.topDown ? 1 : -1;
+ };
- evt = {
- geo: {},
- zoomLevel: val,
- screenPosition: direction,
- eventType: geo.event.zoom
+ /**
+ * Return the DOM element containing a level specific layer. This will
+ * create the element if it doesn't already exist.
+ * @param {number} level The zoom level of the layer to fetch
+ * @return {DOM}
+ */
+ this._getSubLayer = function (level) {
+ var node = this.canvas()
+ .find('div[data-tile-layer=' + level.toFixed() + ']').get(0);
+ if (!node) {
+ node = $(
+ ''
+ ).css('transform-origin', '0px').get(0);
+ this.canvas().append(node);
+ }
+ return node;
};
- if (base) {
- base.geoTrigger(geo.event.zoom, evt, true);
- }
+ /**
+ * Set sublayer transforms to align them with the given zoom level.
+ * @param {number} level The target zoom level
+ */
+ this._updateSubLayers = function (level) {
+ this.canvas().find('.geo-tile-layer').each(function (idx, el) {
+ var $el = $(el),
+ layer = parseInt($el.data('tileLayer'));
+ $el.css(
+ 'transform',
+ 'scale(' + Math.pow(2, level - layer) + ')'
+ );
+ }.bind(this));
+ };
- recenter = evt.center;
- if (!evt.geo.preventDefault) {
+ /**
+ * Update the view according to the map/camera.
+ * @returns {this} Chainable
+ */
+ this._update = function () {
+ var map = this.map(),
+ mapZoom = map.zoom(),
+ zoom = this._options.tileRounding(mapZoom),
+ center = this.displayToLevel(undefined, zoom),
+ bounds = map.bounds(undefined, null),
+ tiles, view = this._getViewBounds(), myPurge = {};
+
+ _deferredPurge = myPurge;
+ tiles = this._getTiles(
+ zoom, bounds, true
+ );
- m_zoom = val;
- m_this._updateBounds();
+ // Update the transform for the local layer coordinates
+ this._updateSubLayers(zoom);
- m_this.children().forEach(function (child) {
- child.geoTrigger(geo.event.zoom, evt, true);
+ var to = this._options.tileOffset(zoom);
+ if (this.renderer() === null) {
+ this.canvas().css(
+ 'transform-origin',
+ 'center center'
+ );
+ this.canvas().css(
+ 'transform',
+ 'scale(' + (Math.pow(2, mapZoom - zoom)) + ')' +
+ 'translate(' +
+ (-to.x) + 'px' + ',' +
+ (-to.y) + 'px' + ')' +
+ 'translate(' +
+ (map.size().width / 2) + 'px' + ',' +
+ (map.size().height / 2) + 'px' + ')' +
+ 'translate(' +
+ (-(view.left + view.right) / 2) + 'px' + ',' +
+ (-(view.bottom + view.top) / 2) + 'px' + ')' +
+ ''
+ );
+ }
+ /* Set some attributes that can be used by non-css based viewers. This
+ * doesn't include the map center, as that may need to be handled
+ * differently from the view center. */
+ this.canvas().attr({
+ scale: Math.pow(2, mapZoom - zoom),
+ dx: -to.x + -(view.left + view.right) / 2,
+ dy: -to.y + -(view.bottom + view.top) / 2
});
- m_this.modified();
- }
+ lastZoom = mapZoom;
+ lastX = center.x;
+ lastY = center.y;
- if (evt.center) {
- m_this.center(recenter);
- } else {
- m_this.pan({x: 0, y: 0});
- }
- return m_this;
- };
+ // reset the tile coverage tree
+ this._tileTree = {};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Pan the map by (x: dx, y: dy) pixels.
- *
- * @param {Object} delta
- * @param {bool?} force Disable bounds clamping
- * @returns {geo.map}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.pan = function (delta, force) {
+ tiles.forEach(function (tile) {
+ tile.then(function () {
+ if (tile !== this.cache.get(tile.toString())) {
+ /* If the tile has fallen out of the cache, don't draw it -- it is
+ * untracked. This may be an indication that a larger cache should
+ * have been used. */
+ return;
+ }
+ /* Check if a tile is still desired. Don't draw it if it isn't. */
+ var mapZoom = map.zoom(),
+ zoom = this._options.tileRounding(mapZoom),
+ bounds = this._getViewBounds();
+ if (this._canPurge(tile, bounds, zoom)) {
+ this.remove(tile);
+ return;
+ }
- var base = m_this.baseLayer(),
- evt, pt, corner1, corner2;
+ this.drawTile(tile);
- if (arg.clampBounds && !force && m_width && m_height) {
- pt = m_this.displayToGcs({
- x: delta.x,
- y: delta.y
- });
+ // mark the tile as covered
+ this._setTileTree(tile);
+ }.bind(this));
- corner1 = m_this.gcsToDisplay({
- x: -180,
- y: 82
- });
- corner2 = m_this.gcsToDisplay({
- x: 180,
- y: -82
+ this.addPromise(tile);
+ }.bind(this));
+
+ // purge all old tiles when the new tiles are loaded (successfully or not)
+ $.when.apply($, tiles)
+ .done(// called on success and failure
+ function () {
+ if (_deferredPurge === myPurge) {
+ this._purge(zoom, true);
+ }
+ }.bind(this)
+ );
+ };
+
+ /**
+ * Set a value in the tile tree object indicating that the given area of
+ * the canvas is covered by the tile.
+ * @protected
+ * @param {geo.tile} tile
+ */
+ this._setTileTree = function (tile) {
+ var index = tile.index;
+ this._tileTree[index.level] = this._tileTree[index.level] || {};
+ this._tileTree[index.level][index.x] = this._tileTree[index.level][index.x] || {};
+ this._tileTree[index.level][index.x][index.y] = tile;
+ };
+
+ /**
+ * Get a value in the tile tree object if it exists or return null.
+ * @protected
+ * @param {object} index A tile index object
+ * @param {object} index.level
+ * @param {object} index.x
+ * @param {object} index.y
+ * @returns {geo.tile|null}
+ */
+ this._getTileTree = function (index) {
+ return (
+ (
+ this._tileTree[index.level] || {}
+ )[index.x] || {}
+ )[index.y] || null;
+ };
+
+ /**
+ * Returns true if the tile is completely covered by other tiles on the canvas.
+ * Currently this method only checks layers +/- 1 away from `tile`. If the
+ * zoom level is allowed to change by 2 or more in a single update step, this
+ * method will need to be refactored to make a more robust check. Returns
+ * an array of tiles covering it or null if any part of the tile is exposed.
+ * @protected
+ * @param {geo.tile} tile
+ * @returns {geo.tile[]|null}
+ */
+ this._isCovered = function (tile) {
+ var level = tile.index.level,
+ x = tile.index.x,
+ y = tile.index.y,
+ tiles = [];
+
+ // Check one level up
+ tiles = this._getTileTree({
+ level: level - 1,
+ x: Math.floor(x / 2),
+ y: Math.floor(y / 2)
});
+ if (tiles) {
+ return [tiles];
+ }
- if (corner1.x > 0 && corner2.x < m_width) {
- // if the map is too small horizontally
- delta.x = (-corner1.x + m_width - corner2.x) / 2;
- } else {
- delta.x = Math.max(Math.min(delta.x, -corner1.x), m_width - corner2.x);
+ // Check one level down
+ tiles = [
+ this._getTileTree({
+ level: level + 1,
+ x: 2 * x,
+ y: 2 * y
+ }),
+ this._getTileTree({
+ level: level + 1,
+ x: 2 * x + 1,
+ y: 2 * y
+ }),
+ this._getTileTree({
+ level: level + 1,
+ x: 2 * x,
+ y: 2 * y + 1
+ }),
+ this._getTileTree({
+ level: level + 1,
+ x: 2 * x + 1,
+ y: 2 * y + 1
+ })
+ ];
+ if (tiles.every(function (t) { return t !== null; })) {
+ return tiles;
+ }
+
+ return null;
+ };
+
+ /**
+ * Returns true if the provided tile is outside of the current view bounds
+ * and can be removed from the canvas.
+ * @protected
+ * @param {geo.tile} tile
+ * @param {object?} bounds The view bounds
+ * @param {object?} bounds.left
+ * @param {object?} bounds.right
+ * @param {object?} bounds.top
+ * @param {object?} bounds.bottom
+ * @returns {boolean}
+ */
+ this._outOfBounds = function (tile, bounds) {
+ /* We may want to add an (n) tile edge buffer so we appear more
+ * responsive */
+ var to = this._options.tileOffset(tile.index.level);
+ var scale = 1;
+ if (tile.index.level !== bounds.level) {
+ scale = Math.pow(2, (bounds.level || 0) - (tile.index.level || 0));
}
- if (corner1.y > 0 && corner2.y < m_height) {
- // if the map is too small horizontally
- delta.y = (-corner1.y + m_height - corner2.y) / 2;
+ return (tile.bottom - to.y) * scale < bounds.top ||
+ (tile.left - to.x) * scale > bounds.right ||
+ (tile.top - to.y) * scale > bounds.bottom ||
+ (tile.right - to.x) * scale < bounds.left;
+ };
+
+ /**
+ * Returns true if the provided tile can be purged from the canvas. This method
+ * will return `true` if the tile is completely covered by one or more other tiles
+ * or it is outside of the active view bounds. This method returns the logical and
+ * of `_isCovered` and `_outOfBounds`.
+ * @protected
+ * @param {geo.tile} tile
+ * @param {object?} bounds The view bounds (if empty, assume global bounds)
+ * @param {number} bounds.left
+ * @param {number} bounds.right
+ * @param {number} bounds.top
+ * @param {number} bounds.bottom
+ * @param {number} bounds.level The zoom level the bounds are given as
+ * @param {number} zoom Keep in bound tile at this zoom level
+ * @param {boolean} doneLoading If true, allow purging additional tiles.
+ * @returns {boolean}
+ */
+ this._canPurge = function (tile, bounds, zoom, doneLoading) {
+ if (this._options.keepLower) {
+ zoom = zoom || 0;
+ if (zoom < tile.index.level) {
+ return true;
+ }
} else {
- delta.y = Math.max(Math.min(delta.y, -corner1.y), m_height - corner2.y);
+ /* For tile layers that should only keep one layer, if loading is
+ * finished, purge all but the current layer. This is important for
+ * semi-transparanet layers. */
+ if ((doneLoading || this._isCovered(tile)) &&
+ zoom !== tile.index.level) {
+ return true;
+ }
}
- }
+ if (bounds) {
+ return this._outOfBounds(tile, bounds);
+ }
+ return false;
+ };
- evt = {
- geo: {},
- screenDelta: delta,
- eventType: geo.event.pan
+ /**
+ * Convert display pixel coordinates (where (0,0) is the upper left) to
+ * layer pixel coordinates (typically (0,0) is the center of the map and
+ * the upper-left has the most negative values).
+ * By default, this is done at the current base zoom level.
+ *
+ * @param pt: the point to convert. If undefined, use the center of the
+ * display.
+ * @param zoom: if specified, the zoom level to use.
+ * @returns: the point in level coordinates.
+ */
+ this.displayToLevel = function (pt, zoom) {
+ var map = this.map(),
+ mapzoom = map.zoom(),
+ roundzoom = this._options.tileRounding(mapzoom),
+ unit = map.unitsPerPixel(zoom === undefined ? roundzoom : zoom);
+ if (pt === undefined) {
+ var size = map.size();
+ pt = {x: size.width / 2, y: size.height / 2};
+ }
+ /* Reverse the y coordinate, since we expect the gcs coordinate system
+ * to be right-handed and the level coordinate system to be
+ * left-handed. */
+ var gcsPt = map.displayToGcs(pt, null),
+ lvlPt = {x: gcsPt.x / unit, y: this._topDown() * gcsPt.y / unit};
+ return lvlPt;
+ };
+
+ /**
+ * Get or set the tile url string or function. If changed, load the new
+ * tiles.
+ *
+ * @param {string|function} [url] The new tile url.
+ * @returns {string|function|this}
+ */
+ this.url = function (url) {
+ if (url === undefined) {
+ return this._options.originalUrl;
+ }
+ if (url === this._options.originalUrl) {
+ return this;
+ }
+ this._options.originalUrl = url;
+ if ($.type(url) === 'string') {
+ url = m_tileUrlFromTemplate(url);
+ }
+ this._options.url = url;
+ this.reset();
+ this.map().draw();
+ return this;
+ };
+
+ /**
+ * Initialize after the layer is added to the map.
+ */
+ this._init = function () {
+ var sublayer;
+
+ // call super method
+ s_init.apply(this, arguments);
+
+ if (this.renderer() === null) {
+ // Initialize sublayers in the correct order
+ for (sublayer = 0; sublayer <= this._options.maxLevel; sublayer += 1) {
+ this._getSubLayer(sublayer);
+ }
+ }
+ return this;
};
- // first pan the base layer
- if (base) {
- base.geoTrigger(geo.event.pan, evt, true);
- }
- // If the base renderer says the pan is invalid, then cancel the action.
- if (evt.geo.preventDefault) {
- return;
- }
- m_center = m_this.displayToGcs({
- x: m_width / 2,
- y: m_height / 2
- });
- m_this._updateBounds();
+ geo.adjustLayerForRenderer('tile', this);
- m_this.children().forEach(function (child) {
- if (child !== base) {
- child.geoTrigger(geo.event.pan, evt, true);
- }
- });
+ return this;
+ };
- m_this.modified();
- return m_this;
+ /**
+ * This object contains the default options used to initialize the tileLayer.
+ */
+ geo.tileLayer.defaults = {
+ minLevel: 0,
+ maxLevel: 18,
+ tileOverlap: 0,
+ tileWidth: 256,
+ tileHeight: 256,
+ wrapX: true,
+ wrapY: false,
+ url: null,
+ subdomains: 'abc',
+ minX: 0,
+ maxX: 255,
+ minY: 0,
+ maxY: 255,
+ tileOffset: function (level) {
+ void(level);
+ return {x: 0, y: 0};
+ },
+ topDown: false,
+ keepLower: true,
+ // cacheSize: 400, // set depending on keepLower
+ tileRounding: Math.round,
+ attribution: '',
+ animationDuration: 0
};
+ inherit(geo.tileLayer, geo.featureLayer);
+})();
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of class fileReader
+ *
+ * @class
+ * @extends geo.object
+ * @returns {geo.fileReader}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.fileReader = function (arg) {
+ "use strict";
+ if (!(this instanceof geo.fileReader)) {
+ return new geo.fileReader(arg);
+ }
+ geo.object.call(this);
+
////////////////////////////////////////////////////////////////////////////
/**
- * Set center of the map to the given geographic coordinates, or get the
- * current center. Uses bare objects {x: 0, y: 0}.
- *
- * @param {Object} coordinates
- * @returns {Object|geo.map}
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this.center = function (coordinates, force) {
- var newCenter, currentCenter;
-
- if (coordinates === undefined) {
- return m_center;
- }
+ arg = arg || {};
- // get the screen coordinates of the new center
- coordinates = geo.util.normalizeCoordinates(coordinates);
- newCenter = m_this.gcsToDisplay(coordinates);
- currentCenter = m_this.gcsToDisplay(m_center);
+ if (!(arg.layer instanceof geo.featureLayer)) {
+ throw "fileReader must be given a feature layer";
+ }
- // call the pan method
- m_this.pan({
- x: currentCenter.x - newCenter.x,
- y: currentCenter.y - newCenter.y
- }, force);
+ var m_layer = arg.layer;
- return m_this;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get the feature layer attached to the reader
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.layer = function () {
+ return m_layer;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set parallel projection setting of the map
- *
- * @returns {Boolean|geo.map}
+ * Tells the caller if it can handle the given file by returning a boolean.
*/
////////////////////////////////////////////////////////////////////////////
- this.parallelProjection = function (val) {
- if (val === undefined) {
- return m_parallelProjection;
- }
- val = val ? true : false;
- if (m_parallelProjection !== val) {
- var base, evt = {
- eventType: geo.event.parallelprojection,
- parallelProjection: val
- };
-
- m_parallelProjection = val;
- base = m_this.baseLayer();
- base.geoTrigger(geo.event.parallelprojection, evt, true);
- m_this.children().forEach(function (child) {
- child.geoTrigger(geo.event.parallelprojection, evt, true);
- });
- m_this.modified();
- }
- return m_this;
+ this.canRead = function () {
+ return false;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Add layer to the map
- *
- * @param {geo.layer} layer to be added to the map
- * @return {geom.map}
+ * Reads the file object and calls the done function when finished. As an
+ * argument to done, provides a boolean that reports if the read was a
+ * success. Possibly, it can call done with an object containing details
+ * of the read operation.
*/
////////////////////////////////////////////////////////////////////////////
- this.createLayer = function (layerName, arg) {
- arg = arg || {};
- arg.parallelProjection = m_parallelProjection;
- var newLayer = geo.createLayer(
- layerName, m_this, arg);
-
- if (newLayer) {
- newLayer._resize(m_x, m_y, m_width, m_height);
- } else {
- return null;
- }
-
- if (newLayer.referenceLayer() || m_this.children().length === 0) {
- m_this.baseLayer(newLayer);
- }
-
- newLayer._resize(m_x, m_y, m_width, m_height); // this call initializes the camera
- m_this.addChild(newLayer);
- m_this.modified();
-
- m_this.geoTrigger(geo.event.layerAdd, {
- type: geo.event.layerAdd,
- target: m_this,
- layer: newLayer
- });
-
- return newLayer;
+ this.read = function (file, done) {
+ done(false);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Remove layer from the map
- *
- * @param {geo.layer} layer that should be removed from the map
- * @return {geo.map}
+ * Return a FileReader with handlers attached.
*/
////////////////////////////////////////////////////////////////////////////
- this.deleteLayer = function (layer) {
-
- if (layer !== null && layer !== undefined) {
- layer._exit();
-
- m_this.removeChild(layer);
-
- m_this.modified();
-
- m_this.geoTrigger(geo.event.layerRemove, {
- type: geo.event.layerRemove,
- target: m_this,
- layer: layer
- });
+ function newFileReader(done, progress) {
+ var reader = new FileReader();
+ if (progress) {
+ reader.onprogress = progress;
}
-
- /// Return deleted layer (similar to createLayer) as in the future
- /// we may provide extension of this method to support deletion of
- /// layer using id or some sort.
- return layer;
- };
+ reader.onloadend = function () {
+ if (!reader.result) {
+ done(reader.error);
+ }
+ done(reader.result);
+ };
+ return reader;
+ }
////////////////////////////////////////////////////////////////////////////
/**
- * Toggle visibility of a layer
- *
- * @param {geo.layer} layer
- * @returns {Boolean}
+ * Private method for reading a file object as a string. Calls done with
+ * the string content when finished or an error object if unsuccessful.
+ * Optionally, the caller can provide a progress method that is called
+ * after reading each slice.
*/
////////////////////////////////////////////////////////////////////////////
- this.toggle = function (layer) {
- if (layer !== null && layer !== undefined) {
- layer.visible(!layer.visible());
- m_this.modified();
-
- m_this.geoTrigger(geo.event.layerToggle, {
- type: geo.event.layerToggle,
- target: m_this,
- layer: layer
- });
- }
- return m_this;
+ this._getString = function (file, done, progress) {
+ var reader = newFileReader(done, progress);
+ reader.readAsText(file);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Resize map
- *
- * @param {Number} x x-offset in display space
- * @param {Number} y y-offset in display space
- * @param {Number} w width in display space
- * @param {Number} h height in display space
+ * Like _getString, but returns an ArrayBuffer object.
*/
////////////////////////////////////////////////////////////////////////////
- this.resize = function (x, y, w, h) {
- var i, layers = m_this.children();
+ this._getArrayBuffer = function (file, done, progress) {
+ var reader = newFileReader(done, progress);
+ reader.readAsText(file);
+ };
- m_x = x;
- m_y = y;
- m_width = w;
- m_height = h;
+ return this;
+};
- for (i = 0; i < layers.length; i += 1) {
- layers[i]._resize(x, y, w, h);
- }
+inherit(geo.fileReader, geo.object);
- m_this.geoTrigger(geo.event.resize, {
- type: geo.event.resize,
- target: m_this,
- x: m_x,
- y: m_y,
- width: w,
- height: h
- });
+/*global File*/
+//////////////////////////////////////////////////////////////////////////////
+/**
+* Create a new instance of class jsonReader
+*
+* @class
+* @extends geo.fileReader
+* @returns {geo.jsonReader}
+*/
+//////////////////////////////////////////////////////////////////////////////
+geo.jsonReader = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.jsonReader)) {
+ return new geo.jsonReader(arg);
+ }
- m_this._updateBounds();
- m_this.pan({x: 0, y: 0});
- m_this.modified();
+ var m_this = this, m_style = arg.style || {};
+ m_style = $.extend({
+ 'strokeWidth': 2,
+ 'strokeColor': {r: 0, g: 0, b: 0},
+ 'strokeOpacity': 1,
+ 'fillColor': {r: 1, g: 0, b: 0},
+ 'fillOpacity': 1
+ }, m_style);
- return m_this;
+ geo.fileReader.call(this, arg);
+
+ this.canRead = function (file) {
+ if (file instanceof File) {
+ return (file.type === 'application/json' || file.name.match(/\.json$/));
+ } else if (typeof file === 'string') {
+ try {
+ JSON.parse(file);
+ } catch (e) {
+ return false;
+ }
+ return true;
+ }
+ try {
+ if (Array.isArray(m_this._featureArray(file))) {
+ return true;
+ }
+ } catch (e) {}
+ return false;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert from gcs coordinates to display coordinates
- *
- * @param {*} input {[[{x:_x, y: _y}], [x1,y1, x2, y2]}
- * @return {object}
- *
- * @note Currently only lat-lon inputs are supported
- */
- ////////////////////////////////////////////////////////////////////////////
- this.gcsToDisplay = function (input) {
- var world, output;
+ this._readObject = function (file, done, progress) {
+ var object;
+ function onDone(fileString) {
+ if (typeof fileString !== 'string') {
+ done(false);
+ }
+
+ // We have two possibilities here:
+ // 1) fileString is a JSON string or is
+ // a URL.
+ try {
+ object = JSON.parse(fileString);
+ done(object);
+ } catch (e) {
+ if (!object) {
+ $.ajax({
+ type: 'GET',
+ url: fileString,
+ dataType: 'text'
+ }).done(function (data) {
+ object = JSON.parse(data);
+ done(object);
+ }).fail(function () {
+ done(false);
+ });
+ }
+ }
+ }
- /// Now handle different data types
- if ((input instanceof Array &&
- input.length > 0) || input instanceof Object) {
- world = m_baseLayer.toLocal(input);
- output = m_baseLayer.renderer().worldToDisplay(world);
+ if (file instanceof File) {
+ m_this._getString(file, onDone, progress);
+ } else if (typeof file === 'string') {
+ onDone(file);
} else {
- /// Everything else
- throw 'Conversion method latLonToDisplay does not handle ' + input;
+ done(file);
}
-
- return output;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert from display to latitude longitude coordinates
- */
- ////////////////////////////////////////////////////////////////////////////
- this.displayToGcs = function (input) {
- var output;
-
- /// Now handle different data types
- if ((input instanceof Array && input.length > 0) ||
- input instanceof Object) {
- output = m_baseLayer.renderer().displayToWorld(input);
- output = m_baseLayer.fromLocal(output);
- } else {
- throw 'Conversion method displayToGcs does not handle ' + input;
+ this._featureArray = function (spec) {
+ if (spec.type === 'FeatureCollection') {
+ return spec.features || [];
+ }
+ if (spec.type === 'GeometryCollection') {
+ throw 'GeometryCollection not yet implemented.';
}
- return output;
+ if (Array.isArray(spec.coordinates)) {
+ return spec;
+ }
+ throw 'Unsupported collection type: ' + spec.type;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Queries each layer for information at this location.
- */
- ////////////////////////////////////////////////////////////////////////////
- this.query = function () {
- // TODO Implement this
+ this._featureType = function (spec) {
+ var geometry = spec.geometry || {};
+ if (geometry.type === 'Point' || geometry.type === 'MultiPoint') {
+ return 'point';
+ }
+ if (geometry.type === 'LineString') {
+ return 'line';
+ }
+ if (geometry.type === 'Polygon') {
+ return 'polygon';
+ }
+ if (geometry.type === 'MultiPolygon') {
+ return 'multipolygon';
+ }
+ return null;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Sets or gets base layer for this map
- *
- * @param {geo.layer} baseLayer optional
- * @returns {geo.map|geo.layer}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.baseLayer = function (baseLayer) {
- var save;
- if (baseLayer !== undefined) {
-
- // The GCS of the layer must match the map
- if (m_gcs !== baseLayer.gcs()) {
- m_this.gcs(baseLayer.gcs());
- }
-
- m_baseLayer = baseLayer;
+ this._getCoordinates = function (spec) {
+ var geometry = spec.geometry || {},
+ coordinates = geometry.coordinates || [], elv;
- // Set the layer as the reference layer
- m_baseLayer.referenceLayer(true);
+ if ((coordinates.length === 2 || coordinates.length === 3) &&
+ (isFinite(coordinates[0]) && isFinite(coordinates[1]))) {
- if (arg.center) {
- // This assumes that the base layer is initially centered at
- // (0, 0). May want to add an explicit call to the base layer
- // to set a given center.
- m_this.center(arg.center, true);
+ // Do we have a elevation component
+ if (isFinite(coordinates[2])) {
+ elv = coordinates[2];
}
- save = m_zoom;
- m_zoom = null;
- m_this.zoom(save);
-
- m_this._updateBounds();
- // This forces the map into a state with valid bounds
- // when clamping is on. The original call to center
- // is forced to initialize the camera position in the
- // base layer so no adjustment is done there.
- m_this.pan({x: 0, y: 0});
- return m_this;
+ // special handling for single point coordinates
+ return [{x: coordinates[0], y: coordinates[1], z: elv}];
}
- return m_baseLayer;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Manually force to render map
- */
- ////////////////////////////////////////////////////////////////////////////
- this.draw = function () {
- var i, layers = m_this.children();
-
- m_this.geoTrigger(geo.event.draw, {
- type: geo.event.draw,
- target: m_this
- }
- );
-
- m_this._update();
- for (i = 0; i < layers.length; i += 1) {
- layers[i].draw();
+ // need better handling here, but we can plot simple polygons
+ // by taking just the outer linearring
+ if (Array.isArray(coordinates[0][0])) {
+ coordinates = coordinates[0];
}
- m_this.geoTrigger(geo.event.drawEnd, {
- type: geo.event.drawEnd,
- target: m_this
- }
- );
-
- return m_this;
+ // return an array of points for LineString, MultiPoint, etc...
+ return coordinates.map(function (c) {
+ return {
+ x: c[0],
+ y: c[1],
+ z: c[2]
+ };
+ });
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Attach a file reader to a layer in the map to be used as a drop target.
- */
- ////////////////////////////////////////////////////////////////////////////
- this.fileReader = function (readerType, opts) {
- var layer, renderer;
- opts = opts || {};
- if (!readerType) {
- return m_fileReader;
- }
- layer = opts.layer;
- if (!layer) {
- renderer = opts.renderer;
- if (!renderer) {
- renderer = 'd3';
- }
- layer = m_this.createLayer('feature', {renderer: renderer});
- }
- opts.layer = layer;
- opts.renderer = renderer;
- m_fileReader = geo.createFileReader(readerType, opts);
- return m_this;
+ this._getStyle = function (spec) {
+ return spec.properties;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Initialize the map
- */
- ////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- var i;
+ this.read = function (file, done, progress) {
- if (m_node === undefined || m_node === null) {
- throw 'Map require DIV node';
- }
+ function _done(object) {
+ var features, allFeatures = [];
- m_node.css('position', 'relative');
- if (arg !== undefined && arg.layers !== undefined) {
- for (i = 0; i < arg.layers.length; i += 1) {
- if (i === 0) {
- m_this.baseLayer(arg.layers[i]);
+ features = m_this._featureArray(object);
+
+ features.forEach(function (feature) {
+ var type = m_this._featureType(feature),
+ coordinates = m_this._getCoordinates(feature),
+ style = m_this._getStyle(feature);
+ if (type) {
+ if (type === 'line') {
+ style.fill = style.fill || false;
+ allFeatures.push(m_this._addFeature(
+ type,
+ [coordinates],
+ style,
+ feature.properties
+ ));
+ } else if (type === 'point') {
+ style.stroke = style.stroke || false;
+ allFeatures.push(m_this._addFeature(
+ type,
+ coordinates,
+ style,
+ feature.properties
+ ));
+ } else if (type === 'polygon') {
+ style.fill = style.fill === undefined ? true : style.fill;
+ style.fillOpacity = (
+ style.fillOpacity === undefined ? 0.25 : style.fillOpacity
+ );
+ // polygons not yet supported
+ allFeatures.push(m_this._addFeature(
+ type,
+ [[coordinates]], //double wrap for the data method below
+ style,
+ feature.properties
+ ));
+ } else if (type === 'multipolygon') {
+ style.fill = style.fill === undefined ? true : style.fill;
+ style.fillOpacity = (
+ style.fillOpacity === undefined ? 0.25 : style.fillOpacity
+ );
+ coordinates = feature.geometry.coordinates.map(function (c) {
+ return [m_this._getCoordinates({
+ geometry: {
+ type: 'Polygon',
+ coordinates: c
+ }
+ })];
+ });
+ allFeatures.push(m_this._addFeature(
+ 'polygon', //there is no multipolygon feature class
+ coordinates,
+ style,
+ feature.properties
+ ));
+ }
+ } else {
+ console.log('unsupported feature type: ' + feature.geometry.type);
}
+ });
- m_this.addLayer(arg.layers[i]);
+ if (done) {
+ done(allFeatures);
}
}
- return m_this;
+
+ m_this._readObject(file, _done, progress);
};
+
////////////////////////////////////////////////////////////////////////////
/**
- * Update map
- */
- ////////////////////////////////////////////////////////////////////////////
- this._update = function (request) {
- var i, layers = m_this.children();
- for (i = 0; i < layers.length; i += 1) {
- layers[i]._update(request);
- }
- return m_this;
+ * Build the data array for a feature given the coordinates and properties
+ * from the geojson.
+ *
+ * @private
+ * @param {Object[]} coordinates Coordinate data array
+ * @param {Object} properties Geojson properties object
+ * @param {Object} style Global style defaults
+ * @returns {Object[]}
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ this._buildData = function (coordinates, properties, style) {
+ return coordinates.map(function (coord) {
+ return {
+ coordinates: coord,
+ properties: properties,
+ style: style
+ };
+ });
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Exit this map
- */
- ////////////////////////////////////////////////////////////////////////////
- this.exit = function () {
- var i, layers = m_this.children();
- for (i = 0; i < layers.length; i += 1) {
- layers[i]._exit();
- }
- if (m_this.interactor()) {
- m_this.interactor().destroy();
- m_this.interactor(null);
+ this._addFeature = function (type, coordinates, style, properties) {
+ var _style = $.extend({}, m_style, style);
+ var feature = m_this.layer().createFeature(type)
+ .data(m_this._buildData(coordinates, properties, style))
+ .style(_style);
+
+ if (type === 'line') {
+ feature.line(function (d) { return d.coordinates; });
+ } else if (type === 'polygon') {
+ feature.position(function (d) {
+ return {
+ x: d.x,
+ y: d.y,
+ z: d.z
+ };
+ }).polygon(function (d) {
+ return {
+ 'outer': d.coordinates[0],
+ 'inner': d.coordinates[1]
+ };
+ });
+ } else {
+ feature.position(function (d) {
+ return d.coordinates;
+ });
}
- m_this.node().off('.geo');
- $(window).off('resize', resizeSelf);
- s_exit();
+ return feature;
};
- this._init(arg);
-
- // set up drag/drop handling
- this.node().on('dragover.geo', function (e) {
- var evt = e.originalEvent;
+};
- if (m_this.fileReader()) {
- evt.stopPropagation();
- evt.preventDefault();
- evt.dataTransfer.dropEffect = 'copy';
- }
- })
- .on('drop.geo', function (e) {
- var evt = e.originalEvent, reader = m_this.fileReader(),
- i, file;
+inherit(geo.jsonReader, geo.fileReader);
- function done() {
- m_this.draw();
- }
+geo.registerFileReader('jsonReader', geo.jsonReader);
- if (reader) {
- evt.stopPropagation();
- evt.preventDefault();
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates a new map object
+ *
+ * Map coordinates for default world map, where c = half circumference at
+ * equator in meters, o = origin:
+ * (-c, c) + o (c, c) + o
+ * (center.x, center.y) + o <-- center of viewport
+ * (-c, -c) + o (c, -c) + o
+ *
+ * @class
+ * @extends geo.sceneObject
+ *
+ * *** Always required ***
+ * @param {string} node DOM selector for the map container
+ *
+ * *** Required when using a domain/CS different from OSM ***
+ * @param {string|geo.transform} [gcs='EPSG:3857']
+ * The main coordinate system of the map
+ * @param {number} [maxZoom=16] The maximum zoom level
+ * @param {string|geo.transform} [ingcs='EPSG:4326']
+ * The default coordinate system of interface calls.
+ * @param {number} [unitsPerPixel=156543] GCS to pixel unit scaling at zoom 0
+ * (i.e. meters per pixel or degrees per pixel).
+ * @param {object?} maxBounds The maximum visable map bounds
+ * @param {number} [maxBounds.left=-20037508] The left bound
+ * @param {number} [maxBounds.right=20037508] The right bound
+ * @param {number} [maxBounds.bottom=-20037508] The bottom bound
+ * @param {number} [maxBounds.top=20037508] The top bound
+ *
+ * *** Initial view ***
+ * @param {number} [zoom=4] Initial zoom
+ * @param {object?} center Map center
+ * @param {number} [center.x=0]
+ * @param {number} [center.y=0]
+ * @param {number?} width The map width (default node width)
+ * @param {number?} height The map height (default node height)
+ *
+ * *** Navigation ***
+ * @param {number} [min=0] Minimum zoom level (though fitting to the viewport
+ * may make it so this is smaller than the smallest possible value)
+ * @param {number} [max=16] Maximum zoom level
+ * @param {boolean} [discreteZoom=false] True to only allow integer zoom
+ * levels. False for any zoom level.
+ *
+ * *** Advanced parameters ***
+ * @param {geo.camera?} camera The camera to control the view
+ * @param {geo.mapInteractor?} interactor The UI event handler
+ * @param {geo.clock?} clock The clock used to synchronize time events
+ * @param {boolean} [autoResize=true] Adjust map size on window resize
+ * @param {boolean} [clampBoundsX=false] Prevent panning outside of the
+ * maximum bounds in the horizontal direction.
+ * @param {boolean} [clampBoundsY=true] Prevent panning outside of the
+ * maximum bounds in the vertical direction.
+ * @param {boolean} [clampZoom=true] Prevent zooming out so that the map area
+ * is smaller than the window.
+ *
+ * @returns {geo.map}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.map = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.map)) {
+ return new geo.map(arg);
+ }
+ arg = arg || {};
+ geo.sceneObject.call(this, arg);
- for (i = 0; i < evt.dataTransfer.files.length; i += 1) {
- file = evt.dataTransfer.files[i];
- if (reader.canRead(file)) {
- reader.read(file, done); // to do: trigger event on done
- }
- }
- }
- });
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Private member variables
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ s_exit = this._exit,
+ // See https://en.wikipedia.org/wiki/Web_Mercator
+ // phiMax = 180 / Math.PI * (2 * Math.atan(Math.exp(Math.PI)) - Math.PI / 2),
+ m_x = 0,
+ m_y = 0,
+ m_node = $(arg.node),
+ m_width = arg.width || m_node.width() || 512,
+ m_height = arg.height || m_node.height() || 512,
+ m_gcs = arg.gcs === undefined ? 'EPSG:3857' : arg.gcs,
+ m_ingcs = arg.ingcs === undefined ? 'EPSG:4326' : arg.ingcs,
+ m_center = {x: 0, y: 0},
+ m_zoom = arg.zoom === undefined ? 4 : arg.zoom,
+ m_fileReader = null,
+ m_interactor = null,
+ m_validZoomRange = {min: 0, max: 16, origMin: 0},
+ m_transition = null,
+ m_queuedTransition = null,
+ m_clock = null,
+ m_discreteZoom = arg.discreteZoom ? true : false,
+ m_maxBounds = arg.maxBounds || {},
+ m_camera = arg.camera || geo.camera(),
+ m_unitsPerPixel,
+ m_clampBoundsX,
+ m_clampBoundsY,
+ m_clampZoom,
+ m_origin,
+ m_scale = {x: 1, y: 1, z: 1}; // constant for the moment
+
+ /* Compute the maximum bounds on our map projection. By default, x ranges
+ * from [-180, 180] in the interface projection, and y matches the x range in
+ * the map (not the interface) projection. For images, this might be
+ * [0, width] and [0, height] instead. */
+ m_maxBounds.left = geo.transform.transformCoordinates(m_ingcs, m_gcs, [{
+ x: m_maxBounds.left !== undefined ? m_maxBounds.left : -180, y: 0}])[0].x;
+ m_maxBounds.right = geo.transform.transformCoordinates(m_ingcs, m_gcs, [{
+ x: m_maxBounds.right !== undefined ? m_maxBounds.right : 180, y: 0}])[0].x;
+ m_maxBounds.top = (m_maxBounds.top !== undefined ?
+ geo.transform.transformCoordinates(m_ingcs, m_gcs, [{
+ x: 0, y: m_maxBounds.top}])[0].y : m_maxBounds.right);
+ m_maxBounds.bottom = (m_maxBounds.bottom !== undefined ?
+ geo.transform.transformCoordinates(m_ingcs, m_gcs, [{
+ x: 0, y: m_maxBounds.bottom}])[0].y : m_maxBounds.left);
+ m_unitsPerPixel = (arg.unitsPerPixel || (
+ m_maxBounds.right - m_maxBounds.left) / 256);
+
+ m_camera.viewport = {width: m_width, height: m_height};
+ arg.center = geo.util.normalizeCoordinates(arg.center);
+ arg.autoResize = arg.autoResize === undefined ? true : arg.autoResize;
+ m_clampBoundsX = arg.clampBoundsX === undefined ? false : arg.clampBoundsX;
+ m_clampBoundsY = arg.clampBoundsY === undefined ? true : arg.clampBoundsY;
+ m_clampZoom = arg.clampZoom === undefined ? true : arg.clampZoom;
////////////////////////////////////////////////////////////////////////////
/**
- * Get or set the map interactor
+ * Get/set the number of world space units per display pixel at the given
+ * zoom level.
+ *
+ * @param {Number} [zoom=0] The target zoom level
+ * @param {Number?} unit If present, set the unitsPerPixel otherwise return
+ * the current value.
+ * @returns {Number|this}
*/
////////////////////////////////////////////////////////////////////////////
- this.interactor = function (arg) {
- if (arg === undefined) {
- return m_interactor;
- }
- m_interactor = arg;
+ this.unitsPerPixel = function (zoom, unit) {
+ zoom = zoom || 0;
+ if (unit) {
+ // get the units at level 0
+ m_unitsPerPixel = Math.pow(2, zoom) * unit;
- // this makes it possible to set a null interactor
- // i.e. map.interactor(null);
- if (m_interactor) {
- m_interactor.map(m_this);
+ // redraw all the things
+ m_this.draw();
+ return m_this;
}
- return m_this;
+ return Math.pow(2, -zoom) * m_unitsPerPixel;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get or set the map clock
+ * Get the map's world coordinate origin in gcs coordinates
+ *
+ * @returns {object}
*/
////////////////////////////////////////////////////////////////////////////
- this.clock = function (arg) {
- if (arg === undefined) {
- return m_clock;
- }
- m_clock = arg;
-
- if (m_clock) {
- m_clock.object(m_this);
- }
- return m_this;
+ this.origin = function () {
+ return $.extend({}, m_origin);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get or set the min/max zoom range.
+ * Get the map's world coordinate scaling relative gcs units
*
- * @param {Object} arg {min: minimumzoom, max: maximumzom}
- * @returns {Object|geo.map}
+ * @returns {object}
*/
////////////////////////////////////////////////////////////////////////////
- this.zoomRange = function (arg) {
- if (arg === undefined) {
- return $.extend({}, m_validZoomRange);
- }
- m_validZoomRange.min = arg.min;
- m_validZoomRange.max = arg.max;
- return m_this;
+ this.scale = function () {
+ return $.extend({}, m_scale);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Start an animated zoom/pan.
- *
- * Options:
- *
- * opts = {
- * center: { x: ... , y: ... } // the new center
- * zoom: ... // the new zoom level
- * duration: ... // the duration (in ms) of the transition
- * ease: ... // an easing function [0, 1] -> [0, 1]
- * }
- *
+ * Get the camera
*
- * Call with no arguments to return the current transition information.
- *
- * @param {object?} opts
- * @returns {geo.map}
+ * @returns {geo.camera}
*/
////////////////////////////////////////////////////////////////////////////
- this.transition = function (opts) {
-
- if (opts === undefined) {
- return m_transition;
- }
-
- if (m_transition) {
- m_queuedTransition = opts;
- return m_this;
- }
-
- function interp1(p0, p1, t) {
- return p0 + (p1 - p0) * t;
- }
- function defaultInterp(p0, p1) {
- return function (t) {
- return [
- interp1(p0[0], p1[0], t),
- interp1(p0[1], p1[1], t),
- interp1(p0[2], p1[2], t)
- ];
- };
- }
-
- // Transform zoom level into z-coordinate and inverse
- function zoom2z(z) {
- return vgl.zoomToHeight(z + 1, m_width, m_height);
- }
- function z2zoom(z) {
- return vgl.heightToZoom(z, m_width, m_height) - 1;
- }
-
- var defaultOpts = {
- center: m_this.center(),
- zoom: m_this.zoom(),
- duration: 1000,
- ease: function (t) {
- return t;
- },
- interp: defaultInterp,
- done: null,
- zCoord: true
- };
-
- if (opts.center) {
- opts.center = geo.util.normalizeCoordinates(opts.center);
- }
- $.extend(defaultOpts, opts);
-
- m_transition = {
- start: {
- center: m_this.center(),
- zoom: m_this.zoom()
- },
- end: {
- center: defaultOpts.center,
- zoom: m_discreteZoom ? Math.round(defaultOpts.zoom) : defaultOpts.zoom
- },
- ease: defaultOpts.ease,
- zCoord: defaultOpts.zCoord,
- done: defaultOpts.done,
- duration: defaultOpts.duration
- };
-
- if (defaultOpts.zCoord) {
- m_transition.interp = defaultOpts.interp(
- [
- m_transition.start.center.x,
- m_transition.start.center.y,
- zoom2z(m_transition.start.zoom)
- ],
- [
- m_transition.end.center.x,
- m_transition.end.center.y,
- zoom2z(m_transition.end.zoom)
- ]
- );
- } else {
- m_transition.interp = defaultOpts.interp(
- [
- m_transition.start.center.x,
- m_transition.start.center.y,
- m_transition.start.zoom
- ],
- [
- m_transition.end.center.x,
- m_transition.end.center.y,
- m_transition.end.zoom
- ]
- );
- }
-
- function anim(time) {
- var done = m_transition.done, next;
- next = m_queuedTransition;
-
- if (!m_transition.start.time) {
- m_transition.start.time = time;
- m_transition.end.time = time + defaultOpts.duration;
- }
- m_transition.time = time - m_transition.start.time;
- if (time >= m_transition.end.time || next) {
- if (!next) {
- m_this.center(m_transition.end.center);
- m_this.zoom(m_transition.end.zoom);
- }
-
- m_transition = null;
-
- m_this.geoTrigger(geo.event.transitionend, defaultOpts);
-
- if (done) {
- done();
- }
-
- if (next) {
- m_queuedTransition = null;
- m_this.transition(next);
- }
-
- return;
- }
-
- var z = m_transition.ease(
- (time - m_transition.start.time) / defaultOpts.duration
- );
-
- var p = m_transition.interp(z);
- if (m_transition.zCoord) {
- p[2] = z2zoom(p[2]);
- }
- m_this.center({
- x: p[0],
- y: p[1]
- });
- m_this.zoom(p[2], undefined, true);
-
- window.requestAnimationFrame(anim);
- }
-
- m_this.geoTrigger(geo.event.transitionstart, defaultOpts);
+ this.camera = function () {
+ return m_camera;
+ };
- if (defaultOpts.cancelNavigation) {
- m_this.geoTrigger(geo.event.transitionend, defaultOpts);
- return m_this;
- } else if (defaultOpts.cancelAnimation) {
- // run the navigation synchronously
- defaultOpts.duration = 0;
- anim(0);
- } else {
- window.requestAnimationFrame(anim);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get map gcs
+ *
+ * @returns {string}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.gcs = function (arg) {
+ if (arg === undefined) {
+ return m_gcs;
}
+ m_gcs = arg;
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update the internally cached map bounds.
- * @private
+ * Get map interface gcs
+ *
+ * @returns {string}
*/
////////////////////////////////////////////////////////////////////////////
- this._updateBounds = function () {
- m_bounds.lowerLeft = m_this.displayToGcs({
- x: 0,
- y: m_height
- });
- m_bounds.lowerRight = m_this.displayToGcs({
- x: m_width,
- y: m_height
- });
- m_bounds.upperLeft = m_this.displayToGcs({
- x: 0,
- y: 0
- });
- m_bounds.upperRight = m_this.displayToGcs({
- x: m_width,
- y: 0
- });
+ this.ingcs = function (arg) {
+ if (arg === undefined) {
+ return m_ingcs;
+ }
+ m_ingcs = arg;
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/set the locations of the current map corners as latitudes/longitudes.
- * When provided the argument should be an object containing the keys
- * lowerLeft and upperRight declaring the desired new map bounds. The
- * new bounds will contain at least the min/max lat/lngs provided. In any
- * case, the actual new bounds will be returned by this function.
+ * Get root node of the map
*
- * @param {geo.geoBounds} [bds] The requested map bounds
- * @return {geo.geoBounds} The actual new map bounds
+ * @returns {object}
*/
////////////////////////////////////////////////////////////////////////////
- this.bounds = function (bds) {
- var nav;
-
- if (bds === undefined) {
- return m_bounds;
- }
-
- nav = m_this.zoomAndCenterFromBounds(bds);
- m_this.zoom(nav.zoom);
- m_this.center(nav.center);
- return m_bounds;
+ this.node = function () {
+ return m_node;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get the center zoom level necessary to display the given lat/lon bounds.
+ * Get/Set zoom level of the map
*
- * @param {geo.geoBounds} [bds] The requested map bounds
- * @return {object} Object containing keys 'center' and 'zoom'
+ * @returns {Number|geo.map}
*/
////////////////////////////////////////////////////////////////////////////
- this.zoomAndCenterFromBounds = function (bds) {
- var ll, ur, dx, dy, zx, zy, center, zoom;
+ this.zoom = function (val, origin, ignoreDiscreteZoom) {
+ var evt, oldZoom, bounds;
+ if (val === undefined) {
+ return m_zoom;
+ }
- // Caveat:
- // Much of the following is invalid for alternative map projections. These
- // computations should really be defered to the base layer, but there is
- // no clear path for doing that with the current base layer api.
+ oldZoom = m_zoom;
+ /* The ignoreDiscreteZoom flag is intended to allow non-integer zoom values
+ * during animation. */
+ val = fix_zoom(val, ignoreDiscreteZoom);
+ if (val === m_zoom) {
+ return m_this;
+ }
- // extract bounds info and check for validity
- ll = geo.util.normalizeCoordinates(bds.lowerLeft || {});
- ur = geo.util.normalizeCoordinates(bds.upperRight || {});
+ m_zoom = val;
- if (ll.x >= ur.x || ll.y >= ur.y) {
- throw new Error('Invalid bounds provided');
- }
+ bounds = m_this.boundsFromZoomAndCenter(val, m_center, null);
+ m_this.modified();
- center = {
- x: (ll.x + ur.x) / 2,
- y: (ll.y + ur.y) / 2
+ camera_bounds(bounds);
+ evt = {
+ geo: {},
+ zoomLevel: m_zoom,
+ screenPosition: origin ? origin.map : undefined,
+ eventType: geo.event.zoom
};
+ m_this.geoTrigger(geo.event.zoom, evt);
- // calculate the current extend
- dx = m_bounds.upperRight.x - m_bounds.lowerLeft.x;
- dy = m_bounds.upperRight.y - m_bounds.lowerLeft.y;
-
- // calculate the zoom levels necessary to fit x and y bounds
- zx = m_zoom - Math.log2((ur.x - ll.x) / dx);
- zy = m_zoom - Math.log2((ur.y - ll.y) / dy);
- zoom = Math.min(zx, zy);
- if (m_discreteZoom) {
- zoom = Math.floor(zoom);
+ if (origin && origin.geo && origin.map) {
+ var shifted = m_this.gcsToDisplay(origin.geo);
+ m_this.pan({x: origin.map.x - shifted.x, y: origin.map.y - shifted.y});
+ } else {
+ m_this.pan({x: 0, y: 0});
}
+ return m_this;
+ };
- return {
- zoom: zoom,
- center: center
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Pan the map by (x: dx, y: dy) pixels.
+ *
+ * @param {Object} delta
+ * @returns {geo.map}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.pan = function (delta) {
+ var evt, unit;
+ evt = {
+ geo: {},
+ screenDelta: delta,
+ eventType: geo.event.pan
};
+
+ unit = m_this.unitsPerPixel(m_zoom);
+
+ m_camera.pan({
+ x: delta.x * unit,
+ y: -delta.y * unit
+ });
+ /* If m_clampBounds* is true, clamp the pan */
+ var bounds = fix_bounds(m_camera.bounds);
+ if (bounds !== m_camera.bounds) {
+ var panPos = m_this.gcsToDisplay({
+ x: m_camera.bounds.left, y: m_camera.bounds.top}, null);
+ camera_bounds(bounds);
+ var clampPos = m_this.gcsToDisplay({
+ x: m_camera.bounds.left, y: m_camera.bounds.top}, null);
+ evt.screenDelta.x += clampPos.x - panPos.x;
+ evt.screenDelta.y += clampPos.y - panPos.y;
+ }
+
+ m_center = m_camera.displayToWorld({
+ x: m_width / 2,
+ y: m_height / 2
+ });
+
+ m_this.geoTrigger(geo.event.pan, evt);
+
+ m_this.modified();
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/set the discrete zoom flag.
+ * Set center of the map to the given geographic coordinates, or get the
+ * current center. Uses bare objects {x: 0, y: 0}.
*
- * @param {bool} If specified, the discrete zoom flag.
- * @return {bool} The current discrete zoom flag if no parameter is
- * specified, otherwise the map object.
+ * @param {Object} coordinates
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform. If setting the
+ * center, they are converted from this gcs to the map projection. The
+ * returned center are converted from the map projection to this gcs.
+ * @returns {Object|geo.map}
*/
////////////////////////////////////////////////////////////////////////////
- this.discreteZoom = function (discreteZoom) {
- if (discreteZoom === undefined) {
- return m_discreteZoom;
+ this.center = function (coordinates, gcs) {
+ var center;
+ if (coordinates === undefined) {
+ center = $.extend({}, m_this.worldToGcs(m_center, gcs));
+ return center;
}
- discreteZoom = discreteZoom ? true : false;
- if (m_discreteZoom !== discreteZoom) {
- m_discreteZoom = discreteZoom;
- if (m_discreteZoom) {
- m_this.zoom(Math.round(m_this.zoom()));
+
+ // get the screen coordinates of the new center
+ m_center = $.extend({}, m_this.gcsToWorld(coordinates, gcs));
+
+ camera_bounds(m_this.boundsFromZoomAndCenter(m_zoom, m_center, null));
+ // trigger a pan event
+ m_this.geoTrigger(
+ geo.event.pan,
+ {
+ geo: coordinates,
+ screenDelta: null,
+ eventType: geo.event.pan
}
- }
+ );
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update the attribution notice displayed on the bottom right corner of
- * the map. The content of this notice is managed by individual layers.
- * This method queries all of the visible layers and joins the individual
- * attribution notices into a single element. By default, this method
- * is called on each of the following events:
- *
- * * geo.event.layerAdd
- * * geo.event.layerRemove
+ * Add layer to the map
*
- * In addition, layers should call this method when their own attribution
- * notices has changed. Users, in general, should not need to call this.
- * @returns {this} Chainable
+ * @param {geo.layer} layer to be added to the map
+ * @return {geom.map}
*/
////////////////////////////////////////////////////////////////////////////
- this.updateAttribution = function () {
- // clear any existing attribution content
- m_this.node().find('.geo-attribution').remove();
+ this.createLayer = function (layerName, arg) {
+ arg = arg || {};
+ var newLayer = geo.createLayer(
+ layerName, m_this, arg);
- // generate a new attribution node
- var $a = $('')
- .addClass('geo-attribution')
- .css({
- position: 'absolute',
- right: '0px',
- bottom: '0px',
- 'padding-right': '5px',
- cursor: 'auto',
- font: '11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif',
- 'z-index': '1001',
- background: 'rgba(255,255,255,0.7)',
- clear: 'both',
- display: 'block',
- 'pointer-events': 'auto'
- }).on('mousedown', function (evt) {
- evt.stopPropagation();
- });
+ if (newLayer) {
- // append content from each layer
- m_this.children().forEach(function (layer) {
- var content = layer.attribution();
- if (content) {
- $('')
- .addClass('geo-attribution-layer')
- .css({
- 'padding-left': '5px'
- })
- .html(content)
- .appendTo($a);
- }
- });
+ m_this.addChild(newLayer);
+ newLayer._update();
+ m_this.modified();
- $a.appendTo(m_this.node());
- return m_this;
+ m_this.geoTrigger(geo.event.layerAdd, {
+ type: geo.event.layerAdd,
+ target: m_this,
+ layer: newLayer
+ });
+ }
+
+ return newLayer;
};
- this.interactor(arg.interactor || geo.mapInteractor());
- this.clock(arg.clock || geo.clock());
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Remove layer from the map
+ *
+ * @param {geo.layer} layer that should be removed from the map
+ * @return {geo.map}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.deleteLayer = function (layer) {
- function resizeSelf() {
- m_this.resize(0, 0, m_node.width(), m_node.height());
- }
+ if (layer !== null && layer !== undefined) {
+ layer._exit();
- if (arg.autoResize) {
- $(window).resize(resizeSelf);
- }
+ m_this.removeChild(layer);
- // attach attribution updates to layer events
- m_this.geoOn([
- geo.event.layerAdd,
- geo.event.layerRemove
- ], m_this.updateAttribution);
+ m_this.modified();
- return this;
-};
+ m_this.geoTrigger(geo.event.layerRemove, {
+ type: geo.event.layerRemove,
+ target: m_this,
+ layer: layer
+ });
+ }
-/**
- * General object specification for map types. Any additional
- * values in the object are passed to the map constructor.
- * @typedef geo.map.spec
- * @type {object}
- * @property {object[]} [data=[]] The default data array to
- * apply to each feature if none exists
- * @property {geo.layer.spec[]} [layers=[]] Layers to create
- */
+ /// Return deleted layer (similar to createLayer) as in the future
+ /// we may provide extension of this method to support deletion of
+ /// layer using id or some sort.
+ return layer;
+ };
-/**
- * Create a map from an object. Any errors in the creation
- * of the map will result in returning null.
- * @param {geo.map.spec} spec The object specification
- * @returns {geo.map|null}
- */
-geo.map.create = function (spec) {
- 'use strict';
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Toggle visibility of a layer
+ *
+ * @param {geo.layer} layer
+ * @returns {Boolean}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.toggle = function (layer) {
+ if (layer !== null && layer !== undefined) {
+ layer.visible(!layer.visible());
+ m_this.modified();
- var map = geo.map(spec);
+ m_this.geoTrigger(geo.event.layerToggle, {
+ type: geo.event.layerToggle,
+ target: m_this,
+ layer: layer
+ });
+ }
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get or set the size of the map.
+ *
+ * @param {Object?} arg
+ * @param {Number} arg.width width in pixels
+ * @param {Number} arg.height height in pixels
+ * @returns {Object} An object containing width and height as keys
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.size = function (arg) {
+ if (arg === undefined) {
+ return {
+ width: m_width,
+ height: m_height
+ };
+ }
+ m_this.resize(0, 0, arg.width, arg.height);
+ return m_this;
+ };
- if (!map) {
- console.warn('Could not create map.');
- return null;
- }
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Resize map (deprecated)
+ *
+ * @param {Number} x x-offset in display space
+ * @param {Number} y y-offset in display space
+ * @param {Number} w width in display space
+ * @param {Number} h height in display space
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.resize = function (x, y, w, h) {
- spec.data = spec.data || [];
- spec.layers = spec.layers || [];
+ // store the original center and restore it after the resize
+ var oldCenter = m_this.center();
+ m_x = x;
+ m_y = y;
+ m_width = w;
+ m_height = h;
- spec.layers.forEach(function (l) {
- l.data = l.data || spec.data;
- l.layer = geo.layer.create(map, l);
- });
+ m_this.camera().viewport = {width: w, height: h};
- return map;
-};
+ reset_minimum_zoom();
+ var newZoom = fix_zoom(m_zoom);
+ if (newZoom !== m_zoom) {
+ m_this.zoom(newZoom);
+ }
-inherit(geo.map, geo.sceneObject);
+ m_this.geoTrigger(geo.event.resize, {
+ type: geo.event.resize,
+ target: m_this,
+ x: m_x,
+ y: m_y,
+ width: w,
+ height: h
+ });
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class feature
- *
- * @class
- * @extends geo.sceneObject
- * @returns {geo.feature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.feature = function (arg) {
- "use strict";
- if (!(this instanceof geo.feature)) {
- return new geo.feature(arg);
- }
- geo.sceneObject.call(this);
+ m_this.center(oldCenter);
+ m_this.modified();
+ return m_this;
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Convert from gcs coordinates to map world coordinates.
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @param {string?} gcs The gcs of the input (map.gcs() by default)
+ * @return {object} World space coordinates
*/
////////////////////////////////////////////////////////////////////////////
- arg = arg || {};
-
- var m_this = this,
- s_exit = this._exit,
- m_selectionAPI = arg.selectionAPI === undefined ? false : arg.selectionAPI,
- m_style = {},
- m_layer = arg.layer === undefined ? null : arg.layer,
- m_gcs = arg.gcs === undefined ? "EPSG:4326" : arg.gcs,
- m_visible = arg.visible === undefined ? true : arg.visible,
- m_bin = arg.bin === undefined ? 0 : arg.bin,
- m_renderer = arg.renderer === undefined ? null : arg.renderer,
- m_dataTime = geo.timestamp(),
- m_buildTime = geo.timestamp(),
- m_updateTime = geo.timestamp(),
- m_selectedFeatures = [];
+ this.gcsToWorld = function (c, gcs) {
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ if (gcs !== m_gcs) {
+ c = geo.transform.transformCoordinates(gcs, m_gcs, [c])[0];
+ }
+ return geo.transform.affineForward(
+ {origin: m_origin},
+ [c]
+ )[0];
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Private method to bind mouse handlers on the map element.
+ * Convert from map world coordinates to gcs coordinates.
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @return {object} GCS space coordinates
*/
////////////////////////////////////////////////////////////////////////////
- this._bindMouseHandlers = function () {
-
- // Don't bind handlers for improved performance on features that don't
- // require it.
- if (!m_selectionAPI) {
- return;
+ this.worldToGcs = function (c, gcs) {
+ c = geo.transform.affineInverse(
+ {origin: m_origin},
+ [c]
+ )[0];
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ if (gcs !== m_gcs) {
+ c = geo.transform.transformCoordinates(m_gcs, gcs, [c])[0];
}
+ return c;
+ };
- // First unbind to be sure that the handlers aren't bound twice.
- m_this._unbindMouseHandlers();
-
- m_this.geoOn(geo.event.mousemove, m_this._handleMousemove);
- m_this.geoOn(geo.event.mouseclick, m_this._handleMouseclick);
- m_this.geoOn(geo.event.brushend, m_this._handleBrushend);
- m_this.geoOn(geo.event.brush, m_this._handleBrush);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Convert from gcs coordinates to display coordinates.
+ *
+ * gcsToWorld | worldToDisplay
+ *
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @return {object} Display space coordinates
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.gcsToDisplay = function (c, gcs) {
+ c = m_this.gcsToWorld(c, gcs);
+ return m_this.worldToDisplay(c);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Private method to unbind mouse handlers on the map element.
+ * Convert from world coordinates to display coordinates using the attached
+ * camera.
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @return {object} Display space coordinates
*/
////////////////////////////////////////////////////////////////////////////
- this._unbindMouseHandlers = function () {
- m_this.geoOff(geo.event.mousemove, m_this._handleMousemove);
- m_this.geoOff(geo.event.mouseclick, m_this._handleMouseclick);
- m_this.geoOff(geo.event.brushend, m_this._handleBrushend);
- m_this.geoOff(geo.event.brush, m_this._handleBrush);
+ this.worldToDisplay = function (c) {
+ return m_camera.worldToDisplay(c);
};
////////////////////////////////////////////////////////////////////////////
/**
- * For binding mouse events, use functions with
- * the following call signatures:
- *
- * function handler(arg) {
- * // arg.data - the data object of the feature
- * // arg.index - the index inside the data array of the featue
- * // arg.mouse - mouse information object (see src/core/mapInteractor.js)
- * }
+ * Convert from display to gcs coordinates
*
- * i.e.
+ * displayToWorld | worldToGcs
*
- * feature.geoOn(geo.event.feature.mousemove, function (arg) {
- * // do something with the feature marker.
- * });
+ * @param {object} c The input display coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @return {object} GCS space coordinates
*/
////////////////////////////////////////////////////////////////////////////
+ this.displayToGcs = function (c, gcs) {
+ c = m_this.displayToWorld(c); // done via camera
+ return m_this.worldToGcs(c, gcs);
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Search for features containing the given point.
- *
- * Returns an object: ::
- *
- * {
- * data: [...] // an array of data objects for matching features
- * index: [...] // an array of indices of the matching features
- * }
- *
- * @argument {Object} coordinate
- * @returns {Object}
+ * Convert from display coordinates to world coordinates using the attached
+ * camera.
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @return {object} World space coordinates
*/
////////////////////////////////////////////////////////////////////////////
- this.pointSearch = function () {
- // base class method does nothing
- return {
- index: [],
- found: []
- };
+ this.displayToWorld = function (c) {
+ return m_camera.displayToWorld(c);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Private mousemove handler
+ * Manually force to render map
*/
////////////////////////////////////////////////////////////////////////////
- this._handleMousemove = function () {
- var mouse = m_this.layer().map().interactor().mouse(),
- data = m_this.data(),
- over = m_this.pointSearch(mouse.geo),
- newFeatures = [], oldFeatures = [], lastTop = -1, top = -1;
+ this.draw = function () {
+ var i, layers = m_this.children();
- // Get the index of the element that was previously on top
- if (m_selectedFeatures.length) {
- lastTop = m_selectedFeatures[m_selectedFeatures.length - 1];
+ m_this.geoTrigger(geo.event.draw, {
+ type: geo.event.draw,
+ target: m_this
+ }
+ );
+
+ m_this._update();
+
+ for (i = 0; i < layers.length; i += 1) {
+ layers[i].draw();
}
- // There are probably faster ways of doing this:
- newFeatures = over.index.filter(function (i) {
- return m_selectedFeatures.indexOf(i) < 0;
- });
- oldFeatures = m_selectedFeatures.filter(function (i) {
- return over.index.indexOf(i) < 0;
- });
+ m_this.geoTrigger(geo.event.drawEnd, {
+ type: geo.event.drawEnd,
+ target: m_this
+ }
+ );
- geo.feature.eventID += 1;
- // Fire events for mouse in first.
- newFeatures.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.mouseover, {
- data: data[i],
- index: i,
- mouse: mouse,
- eventID: geo.feature.eventID,
- top: idx === newFeatures.length - 1
- }, true);
- });
+ return m_this;
+ };
- geo.feature.eventID += 1;
- // Fire events for mouse out next
- oldFeatures.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.mouseout, {
- data: data[i],
- index: i,
- mouse: mouse,
- eventID: geo.feature.eventID,
- top: idx === oldFeatures.length - 1
- }, true);
- });
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Attach a file reader to a layer in the map to be used as a drop target.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.fileReader = function (readerType, opts) {
+ var layer, renderer;
+ opts = opts || {};
+ if (!readerType) {
+ return m_fileReader;
+ }
+ layer = opts.layer;
+ if (!layer) {
+ renderer = opts.renderer;
+ if (!renderer) {
+ renderer = 'd3';
+ }
+ layer = m_this.createLayer('feature', {renderer: renderer});
+ }
+ opts.layer = layer;
+ opts.renderer = renderer;
+ m_fileReader = geo.createFileReader(readerType, opts);
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize the map
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function () {
+
+ if (m_node === undefined || m_node === null) {
+ throw 'Map require DIV node';
+ }
+
+ m_node.css('position', 'relative');
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Update map
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._update = function (request) {
+ var i, layers = m_this.children();
+ for (i = 0; i < layers.length; i += 1) {
+ layers[i]._update(request);
+ }
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Exit this map
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.exit = function () {
+ var i, layers = m_this.children();
+ for (i = 0; i < layers.length; i += 1) {
+ layers[i]._exit();
+ }
+ if (m_this.interactor()) {
+ m_this.interactor().destroy();
+ m_this.interactor(null);
+ }
+ m_this.node().off('.geo');
+ $(window).off('resize', resizeSelf);
+ s_exit();
+ };
- geo.feature.eventID += 1;
- // Fire events for mouse move last
- over.index.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.mousemove, {
- data: data[i],
- index: i,
- mouse: mouse,
- eventID: geo.feature.eventID,
- top: idx === over.index.length - 1
- }, true);
- });
+ this._init(arg);
- // Replace the selected features array
- m_selectedFeatures = over.index;
+ // set up drag/drop handling
+ this.node().on('dragover.geo', function (e) {
+ var evt = e.originalEvent;
- // Get the index of the element that is now on top
- if (m_selectedFeatures.length) {
- top = m_selectedFeatures[m_selectedFeatures.length - 1];
+ if (m_this.fileReader()) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ evt.dataTransfer.dropEffect = 'copy';
+ }
+ })
+ .on('drop.geo', function (e) {
+ var evt = e.originalEvent, reader = m_this.fileReader(),
+ i, file;
+
+ function done() {
+ m_this.draw();
}
- if (lastTop !== top) {
- // The element on top changed so we need to fire mouseon/mouseoff
- if (lastTop !== -1) {
- m_this.geoTrigger(geo.event.feature.mouseoff, {
- data: data[lastTop],
- index: lastTop,
- mouse: mouse
- }, true);
- }
+ if (reader) {
+ evt.stopPropagation();
+ evt.preventDefault();
- if (top !== -1) {
- m_this.geoTrigger(geo.event.feature.mouseon, {
- data: data[top],
- index: top,
- mouse: mouse
- }, true);
+ for (i = 0; i < evt.dataTransfer.files.length; i += 1) {
+ file = evt.dataTransfer.files[i];
+ if (reader.canRead(file)) {
+ reader.read(file, done); // to do: trigger event on done
+ }
}
}
- };
+ });
////////////////////////////////////////////////////////////////////////////
/**
- * Private mouseclick handler
+ * Get or set the map interactor
*/
////////////////////////////////////////////////////////////////////////////
- this._handleMouseclick = function () {
- var mouse = m_this.layer().map().interactor().mouse(),
- data = m_this.data(),
- over = m_this.pointSearch(mouse.geo);
+ this.interactor = function (arg) {
+ if (arg === undefined) {
+ return m_interactor;
+ }
+ m_interactor = arg;
- geo.feature.eventID += 1;
- over.index.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.mouseclick, {
- data: data[i],
- index: i,
- mouse: mouse,
- eventID: geo.feature.eventID,
- top: idx === over.index.length - 1
- }, true);
- });
+ // this makes it possible to set a null interactor
+ // i.e. map.interactor(null);
+ if (m_interactor) {
+ m_interactor.map(m_this);
+ }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Private brush handler.
+ * Get or set the map clock
*/
////////////////////////////////////////////////////////////////////////////
- this._handleBrush = function (brush) {
- var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
- data = m_this.data();
+ this.clock = function (arg) {
+ if (arg === undefined) {
+ return m_clock;
+ }
+ m_clock = arg;
- geo.feature.eventID += 1;
- idx.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.brush, {
- data: data[i],
- index: i,
- mouse: brush.mouse,
- brush: brush,
- eventID: geo.feature.eventID,
- top: idx === idx.length - 1
- }, true);
- });
+ if (m_clock) {
+ m_clock.object(m_this);
+ }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Private brushend handler.
+ * Get or set the min/max zoom range.
+ *
+ * @param {Object} arg {min: minimumzoom, max: maximumzom}
+ * @returns {Object|geo.map}
*/
////////////////////////////////////////////////////////////////////////////
- this._handleBrushend = function (brush) {
- var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
- data = m_this.data();
+ this.zoomRange = function (arg) {
+ if (arg === undefined) {
+ return $.extend({}, m_validZoomRange);
+ }
+ if (arg.max !== undefined) {
+ m_validZoomRange.max = arg.max;
+ }
- geo.feature.eventID += 1;
- idx.forEach(function (i, idx) {
- m_this.geoTrigger(geo.event.feature.brushend, {
- data: data[i],
- index: i,
- mouse: brush.mouse,
- brush: brush,
- eventID: geo.feature.eventID,
- top: idx === idx.length - 1
- }, true);
- });
+ // don't allow the minimum zoom to go below what will
+ // fit in the view port
+ if (arg.min !== undefined) {
+ m_validZoomRange.min = m_validZoomRange.origMin = fix_zoom(arg.min);
+ }
+ reset_minimum_zoom();
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set style used by the feature
+ * Start an animated zoom/pan.
+ *
+ * Options:
+ *
+ * opts = {
+ * center: { x: ... , y: ... } // the new center
+ * zoom: ... // the new zoom level
+ * duration: ... // the duration (in ms) of the transition
+ * ease: ... // an easing function [0, 1] -> [0, 1]
+ * }
+ *
+ *
+ * Call with no arguments to return the current transition information.
+ *
+ * @param {object?} opts
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform. Applies only to the
+ * center coordinate of the opts and to converting zoom values to height,
+ * if specified.
+ * @returns {geo.map}
*/
////////////////////////////////////////////////////////////////////////////
- this.style = function (arg1, arg2) {
- if (arg1 === undefined) {
- return m_style;
- } else if (typeof arg1 === "string" && arg2 === undefined) {
- return m_style[arg1];
- } else if (arg2 === undefined) {
- m_style = $.extend({}, m_style, arg1);
- m_this.modified();
- return m_this;
- } else {
- m_style[arg1] = arg2;
- m_this.modified();
+ this.transition = function (opts, gcs) {
+
+ if (opts === undefined) {
+ return m_transition;
+ }
+
+ if (m_transition) {
+ m_queuedTransition = opts;
return m_this;
}
- };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * A uniform getter that always returns a function even for constant styles.
- * Maybe extend later to support accessor-like objects. If undefined input,
- * return all the styles as an object.
- *
- * @param {string|undefined} key
- * @return {function}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.style.get = function (key) {
- var tmp, out;
- if (key === undefined) {
- var all = {}, k;
- for (k in m_style) {
- if (m_style.hasOwnProperty(k)) {
- all[k] = m_this.style.get(k);
- }
- }
- return all;
+ function interp1(p0, p1, t) {
+ return p0 + (p1 - p0) * t;
}
- if (key.toLowerCase().match(/color$/)) {
- if (geo.util.isFunction(m_style[key])) {
- tmp = geo.util.ensureFunction(m_style[key]);
- out = function () {
- return geo.util.convertColor(
- tmp.apply(this, arguments)
- );
- };
- } else {
- // if the color is not a function, only convert it once
- out = geo.util.ensureFunction(geo.util.convertColor(m_style[key]));
+ function defaultInterp(p0, p1) {
+ return function (t) {
+ return [
+ interp1(p0[0], p1[0], t),
+ interp1(p0[1], p1[1], t),
+ interp1(p0[2], p1[2], t)
+ ];
+ };
+ }
+
+ var units = m_this.unitsPerPixel(0);
+
+ // Transform zoom level into z-coordinate and inverse
+ function zoom2z(z) {
+ return vgl.zoomToHeight(z + 1, m_width, m_height) * units;
+ }
+ function z2zoom(z) {
+ return vgl.heightToZoom(z / units, m_width, m_height) - 1;
+ }
+
+ var defaultOpts = {
+ center: m_this.center(undefined, null),
+ zoom: m_this.zoom(),
+ duration: 1000,
+ ease: function (t) {
+ return t;
+ },
+ interp: defaultInterp,
+ done: null,
+ zCoord: true
+ };
+
+ if (opts.center) {
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ opts.center = geo.util.normalizeCoordinates(opts.center);
+ if (gcs !== m_gcs) {
+ opts.center = geo.transform.transformCoordinates(gcs, m_gcs, [
+ opts.center])[0];
}
+ }
+ $.extend(defaultOpts, opts);
+
+ m_transition = {
+ start: {
+ center: m_this.center(undefined, null),
+ zoom: m_this.zoom()
+ },
+ end: {
+ center: defaultOpts.center,
+ zoom: fix_zoom(defaultOpts.zoom)
+ },
+ ease: defaultOpts.ease,
+ zCoord: defaultOpts.zCoord,
+ done: defaultOpts.done,
+ duration: defaultOpts.duration
+ };
+
+ if (defaultOpts.zCoord) {
+ m_transition.interp = defaultOpts.interp(
+ [
+ m_transition.start.center.x,
+ m_transition.start.center.y,
+ zoom2z(m_transition.start.zoom)
+ ],
+ [
+ m_transition.end.center.x,
+ m_transition.end.center.y,
+ zoom2z(m_transition.end.zoom)
+ ]
+ );
} else {
- out = geo.util.ensureFunction(m_style[key]);
+ m_transition.interp = defaultOpts.interp(
+ [
+ m_transition.start.center.x,
+ m_transition.start.center.y,
+ m_transition.start.zoom
+ ],
+ [
+ m_transition.end.center.x,
+ m_transition.end.center.y,
+ m_transition.end.zoom
+ ]
+ );
}
- return out;
- };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get layer referenced by the feature
- */
- ////////////////////////////////////////////////////////////////////////////
- this.layer = function () {
- return m_layer;
- };
+ function anim(time) {
+ var done = m_transition.done, next;
+ next = m_queuedTransition;
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get renderer used by the feature
- */
- ////////////////////////////////////////////////////////////////////////////
- this.renderer = function () {
- return m_renderer;
- };
+ if (!m_transition.start.time) {
+ m_transition.start.time = time;
+ m_transition.end.time = time + defaultOpts.duration;
+ }
+ m_transition.time = time - m_transition.start.time;
+ if (time >= m_transition.end.time || next) {
+ if (!next) {
+ m_this.center(m_transition.end.center, null);
+ m_this.zoom(m_transition.end.zoom);
+ }
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get list of drawables or nodes that are context/api specific.
- */
- ////////////////////////////////////////////////////////////////////////////
- this.drawables = function () {
- return m_this._drawables();
- };
+ m_transition = null;
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set projection of the feature
- */
- ////////////////////////////////////////////////////////////////////////////
- this.gcs = function (val) {
- if (val === undefined) {
- return m_gcs;
- } else {
- m_gcs = val;
- m_this.modified();
- return m_this;
- }
- };
+ m_this.geoTrigger(geo.event.transitionend, defaultOpts);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set visibility of the feature
- */
- ////////////////////////////////////////////////////////////////////////////
- this.visible = function (val) {
- if (val === undefined) {
- return m_visible;
- } else {
- m_visible = val;
- m_this.modified();
+ if (done) {
+ done();
+ }
- // bind or unbind mouse handlers on visibility change
- if (m_visible) {
- m_this._bindMouseHandlers();
- } else {
- m_this._unbindMouseHandlers();
+ if (next) {
+ m_queuedTransition = null;
+ m_this.transition(next);
+ }
+
+ return;
}
- return m_this;
- }
- };
+ var z = m_transition.ease(
+ (time - m_transition.start.time) / defaultOpts.duration
+ );
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set bin of the feature
- *
- * Bin number is typically used for sorting the order of rendering
- */
- ////////////////////////////////////////////////////////////////////////////
- this.bin = function (val) {
- if (val === undefined) {
- return m_bin;
- } else {
- m_bin = val;
- m_this.modified();
- return m_this;
- }
- };
+ var p = m_transition.interp(z);
+ if (m_transition.zCoord) {
+ p[2] = z2zoom(p[2]);
+ }
+ m_this.center({
+ x: p[0],
+ y: p[1]
+ }, null);
+ m_this.zoom(p[2], undefined, true);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set timestamp of data change
- */
- ////////////////////////////////////////////////////////////////////////////
- this.dataTime = function (val) {
- if (val === undefined) {
- return m_dataTime;
- } else {
- m_dataTime = val;
- m_this.modified();
- return m_this;
+ window.requestAnimationFrame(anim);
}
- };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set timestamp of last time build happened
- */
- ////////////////////////////////////////////////////////////////////////////
- this.buildTime = function (val) {
- if (val === undefined) {
- return m_buildTime;
- } else {
- m_buildTime = val;
- m_this.modified();
+ m_this.geoTrigger(geo.event.transitionstart, defaultOpts);
+
+ if (defaultOpts.cancelNavigation) {
+ m_this.geoTrigger(geo.event.transitionend, defaultOpts);
return m_this;
+ } else if (defaultOpts.cancelAnimation) {
+ // run the navigation synchronously
+ defaultOpts.duration = 0;
+ anim(0);
+ } else {
+ window.requestAnimationFrame(anim);
}
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set timestamp of last time update happened
+ * Get/set the locations of the current map corners as latitudes/longitudes.
+ * When provided the argument should be an object containing the keys
+ * lowerLeft and upperRight declaring the desired new map bounds. The
+ * new bounds will contain at least the min/max lat/lngs provided. In any
+ * case, the actual new bounds will be returned by this function.
+ *
+ * @param {geo.geoBounds} [bds] The requested map bounds
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform. If setting the
+ * bounds, they are converted from this gcs to the map projection. The
+ * returned bounds are converted from the map projection to this gcs.
+ * @return {geo.geoBounds} The actual new map bounds
*/
////////////////////////////////////////////////////////////////////////////
- this.updateTime = function (val) {
- if (val === undefined) {
- return m_updateTime;
- } else {
- m_updateTime = val;
- m_this.modified();
- return m_this;
+ this.bounds = function (bds, gcs) {
+ var nav;
+
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ if (bds !== undefined) {
+ if (gcs !== m_gcs) {
+ var trans = geo.transform.transformCoordinates(gcs, m_gcs, [{
+ x: bds.left, y: bds.top}, {x: bds.right, y: bds.bottom}]);
+ bds = {
+ left: trans[0].x,
+ top: trans[0].y,
+ right: trans[1].x,
+ bottom: trans[1].y
+ };
+ }
+ bds = fix_bounds(bds);
+ nav = m_this.zoomAndCenterFromBounds(bds, null);
+
+ // This might have concequences in terms of bounds/zoom clamping.
+ // What behavior do we expect from this method in that case?
+ m_this.zoom(nav.zoom);
+ m_this.center(nav.center, null);
}
+
+ return m_this.boundsFromZoomAndCenter(m_zoom, m_center, gcs);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set data
+ * Get the center zoom level necessary to display the given lat/lon bounds.
*
- * @returns {Array}
+ * @param {geo.geoBounds} [bds] The requested map bounds
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @return {object} Object containing keys 'center' and 'zoom'
*/
////////////////////////////////////////////////////////////////////////////
- this.data = function (data) {
- if (data === undefined) {
- return m_this.style("data") || [];
- } else {
- m_this.style("data", data);
- m_this.dataTime().modified();
- m_this.modified();
- return m_this;
+ this.zoomAndCenterFromBounds = function (bounds, gcs) {
+ var center, zoom;
+
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ if (gcs !== m_gcs) {
+ var trans = geo.transform.transformCoordinates(gcs, m_gcs, [{
+ x: bounds.left, y: bounds.top}, {x: bounds.right, y: bounds.bottom}]);
+ bounds = {
+ left: trans[0].x,
+ top: trans[0].y,
+ right: trans[1].x,
+ bottom: trans[1].y
+ };
+ }
+ if (bounds.left >= bounds.right || bounds.bottom >= bounds.top) {
+ throw new Error('Invalid bounds provided');
}
- };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Query if the selection API is enabled for this feature.
- * @returns {bool}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.selectionAPI = function () {
- return m_selectionAPI;
+ // calculate the zoom to fit the bounds
+ zoom = fix_zoom(calculate_zoom(bounds));
+
+ // clamp bounds if necessary
+ bounds = fix_bounds(bounds);
+
+ /* This relies on having the map projection coordinates be uniform
+ * regardless of location. If not, the center will not be correct. */
+ // calculate new center
+ center = {
+ x: (bounds.left + bounds.right) / 2 - m_origin.x,
+ y: (bounds.top + bounds.bottom) / 2 - m_origin.y
+ };
+
+ return {
+ zoom: zoom,
+ center: center
+ };
};
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Get the bounds that will be displayed with the given zoom and center.
*
- * Derived class should implement this
+ * Note: the bounds may not have the requested zoom and center due to map
+ * restrictions.
+ *
+ * @param {number} zoom The requested zoom level
+ * @param {geo.geoPosition} center The requested center
+ * @param {string|geo.transform} [gcs] undefined to use the interface gcs,
+ * null to use the map gcs, or any other transform.
+ * @return {geo.geoBounds}
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- if (!m_layer) {
- throw "Feature requires a valid layer";
- }
- m_style = $.extend({},
- {"opacity": 1.0}, arg.style === undefined ? {} :
- arg.style);
- m_this._bindMouseHandlers();
+ this.boundsFromZoomAndCenter = function (zoom, center, gcs) {
+ var width, height, bounds, units;
+
+ gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs));
+ // preprocess the arguments
+ zoom = fix_zoom(zoom);
+ units = m_this.unitsPerPixel(zoom);
+ center = m_this.gcsToWorld(center, gcs);
+
+ // get half the width and height in world coordinates
+ width = m_width * units / 2;
+ height = m_height * units / 2;
+
+ // calculate the bounds. This is only valid if the map projection has
+ // uniform units in each direction. If not, then worldToGcs should be
+ // used.
+ bounds = {
+ left: center.x - width + m_origin.x,
+ right: center.x + width + m_origin.x,
+ bottom: center.y - height + m_origin.y,
+ top: center.y + height + m_origin.y
+ };
+
+ // correct the bounds when clamping is enabled
+ return fix_bounds(bounds);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Build
+ * Get/set the discrete zoom flag.
*
- * Derived class should implement this
+ * @param {bool} If specified, the discrete zoom flag.
+ * @return {bool} The current discrete zoom flag if no parameter is
+ * specified, otherwise the map object.
*/
////////////////////////////////////////////////////////////////////////////
- this._build = function () {
+ this.discreteZoom = function (discreteZoom) {
+ if (discreteZoom === undefined) {
+ return m_discreteZoom;
+ }
+ discreteZoom = discreteZoom ? true : false;
+ if (m_discreteZoom !== discreteZoom) {
+ m_discreteZoom = discreteZoom;
+ if (m_discreteZoom) {
+ m_this.zoom(Math.round(m_this.zoom()));
+ }
+ }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get context specific drawables
- *
- * Derived class should implement this
+ * Get the layers contained in the map.
+ * Alias of {@linkcode geo.sceneObject.children}.
*/
////////////////////////////////////////////////////////////////////////////
- this._drawables = function () {
- };
+ this.layers = this.children;
////////////////////////////////////////////////////////////////////////////
/**
- * Update
+ * Update the attribution notice displayed on the bottom right corner of
+ * the map. The content of this notice is managed by individual layers.
+ * This method queries all of the visible layers and joins the individual
+ * attribution notices into a single element. By default, this method
+ * is called on each of the following events:
*
- * Derived class should implement this
+ * * geo.event.layerAdd
+ * * geo.event.layerRemove
+ *
+ * In addition, layers should call this method when their own attribution
+ * notices has changed. Users, in general, should not need to call this.
+ * @returns {this} Chainable
*/
////////////////////////////////////////////////////////////////////////////
- this._update = function () {
+ this.updateAttribution = function () {
+ // clear any existing attribution content
+ m_this.node().find('.geo-attribution').remove();
+
+ // generate a new attribution node
+ var $a = $('')
+ .addClass('geo-attribution')
+ .css({
+ position: 'absolute',
+ right: '0px',
+ bottom: '0px',
+ 'padding-right': '5px',
+ cursor: 'auto',
+ font: '11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif',
+ 'z-index': '1001',
+ background: 'rgba(255,255,255,0.7)',
+ clear: 'both',
+ display: 'block',
+ 'pointer-events': 'auto'
+ }).on('mousedown', function (evt) {
+ evt.stopPropagation();
+ });
+
+ // append content from each layer
+ m_this.children().forEach(function (layer) {
+ var content = layer.attribution();
+ if (content) {
+ $('')
+ .addClass('geo-attribution-layer')
+ .css({
+ 'padding-left': '5px'
+ })
+ .html(content)
+ .appendTo($a);
+ }
+ });
+
+ $a.appendTo(m_this.node());
+ return m_this;
};
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // The following are some private methods for interacting with the camera.
+ // In order to hide the complexity of dealing with map aspect ratios,
+ // clamping behavior, reseting zoom levels on resize, etc. from the
+ // layers, the map handles camera movements directly. This requires
+ // passing all camera movement events through the map initially. The
+ // map uses these methods to fix up the events according to the constraints
+ // of the display and passes the event to the layers.
+ //
////////////////////////////////////////////////////////////////////////////
/**
- * Destroy
- *
- * Derived class should implement this
+ * Calculate the scaling factor to fit the given map bounds
+ * into the viewport with the correct aspect ratio.
+ * @param {object} bounds A desired bounds
+ * @return {object} Multiplicative aspect ratio correction
+ * @private
*/
- ////////////////////////////////////////////////////////////////////////////
- this._exit = function () {
- m_this._unbindMouseHandlers();
- m_selectedFeatures = [];
- m_style = {};
- arg = {};
- s_exit();
- };
-
- this._init(arg);
- return this;
-};
-
-/**
- * The most recent feature event triggered.
- * @type {number}
- */
-geo.feature.eventID = 0;
-
-/**
- * General object specification for feature types.
- * @typedef geo.feature.spec
- * @type {object}
- * @property {string} type A supported feature type.
- * @property {object[]} [data=[]] An array of arbitrary objects used to
- * construct the feature. These objects (and their associated
- * indices in the array) will be passed back to style and attribute
- * accessors provided by the user. In general the number of
- * "markers" drawn will be equal to the length of this array.
- */
+ function camera_scaling(bounds) {
+ var width = bounds.right - bounds.left,
+ height = bounds.top - bounds.bottom,
+ ar_bds = Math.abs(width / height),
+ ar_vp = m_width / m_height,
+ sclx, scly;
-/**
- * Create a feature from an object. The implementation here is
- * meant to define the general interface of creating features
- * from a javascript object. See documentation from individual
- * feature types for specific details. In case of an error in
- * the arguments this method will return null;
- * @param {geo.layer} layer The layer to add the feature to
- * @param {geo.feature.spec} [spec={}] The object specification
- * @returns {geo.feature|null}
- */
-geo.feature.create = function (layer, spec) {
- "use strict";
+ if (ar_bds > ar_vp) {
+ // fit left and right
+ sclx = 1;
- var type = spec.type;
+ // grow top and bottom
+ scly = ar_bds / ar_vp;
+ } else {
+ // fit top and bottom
+ scly = 1;
- // Check arguments
- if (!layer instanceof geo.layer) {
- console.warn("Invalid layer");
- return null;
- }
- if (typeof spec !== "object") {
- console.warn("Invalid spec");
- return null;
- }
- var feature = layer.createFeature(type);
- if (!feature) {
- console.warn("Could not create feature type '" + type + "'");
- return null;
+ // grow left and right
+ sclx = ar_vp / ar_bds;
+ }
+ return {x: sclx, y: scly};
}
- spec = spec || {};
- spec.data = spec.data || [];
- return feature.style(spec);
-};
-
-inherit(geo.feature, geo.sceneObject);
+ /**
+ * Calculate the minimum zoom level to fit the given
+ * bounds inside the view port using the view port size,
+ * the given bounds, and the number of units per
+ * pixel. The method sets the valid zoom bounds as well
+ * as the current zoom level to be within that range.
+ * @private
+ */
+ function calculate_zoom(bounds) {
+ // compare the aspect ratios of the viewport and bounds
+ var scl = camera_scaling(bounds), z;
+
+ if (scl.y > scl.x) {
+ // left to right matches exactly
+ // center map vertically and have blank borders on the
+ // top and bottom (or repeat tiles)
+ z = -Math.log2(
+ Math.abs(bounds.right - bounds.left) * scl.x /
+ (m_width * m_unitsPerPixel)
+ );
+ } else {
+ // top to bottom matches exactly, blank border on the
+ // left and right (or repeat tiles)
+ z = -Math.log2(
+ Math.abs(bounds.top - bounds.bottom) * scl.y /
+ (m_height * m_unitsPerPixel)
+ );
+ }
+ return z;
+ }
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class pointFeature
- *
- * @class
- * @param {object} arg Options object
- * @param {boolean} arg.clustering Enable point clustering
- * @extends geo.feature
- * @returns {geo.pointFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.pointFeature = function (arg) {
- "use strict";
- if (!(this instanceof geo.pointFeature)) {
- return new geo.pointFeature(arg);
+ /**
+ * Reset the minimum zoom level given the current window size.
+ * @private
+ */
+ function reset_minimum_zoom() {
+ if (m_clampZoom) {
+ m_validZoomRange.min = Math.max(
+ m_validZoomRange.origMin, calculate_zoom(m_maxBounds));
+ } else {
+ m_validZoomRange.min = m_validZoomRange.origMin;
+ }
}
- arg = arg || {};
- geo.feature.call(this, arg);
- ////////////////////////////////////////////////////////////////////////////
/**
+ * Return the nearest valid zoom level to the requested zoom.
* @private
*/
- ////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init,
- m_rangeTree = null,
- m_rangeTreeTime = geo.timestamp(),
- s_data = this.data,
- m_maxRadius = 0,
- m_clustering = arg.clustering,
- m_clusterTree = null,
- m_allData = [],
- m_lastZoom = null,
- m_ignoreData = false; // flag to ignore data() calls made locally
+ function fix_zoom(zoom, ignoreDiscreteZoom) {
+ zoom = Math.max(
+ Math.min(
+ m_validZoomRange.max,
+ zoom
+ ),
+ m_validZoomRange.min
+ );
+ if (m_discreteZoom && !ignoreDiscreteZoom) {
+ zoom = Math.round(zoom);
+ if (zoom < m_validZoomRange.min) {
+ zoom = Math.ceil(m_validZoomRange.min);
+ }
+ }
+ return zoom;
+ }
- ////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set clustering option
- *
- * @returns {geo.pointFeature|boolean}
+ * Return the nearest valid bounds maintaining the
+ * width and height. Does nothing if m_clampBounds* is
+ * false.
+ * @private
*/
- ////////////////////////////////////////////////////////////////////////////
- this.clustering = function (val) {
- if (val === undefined) {
- return m_clustering;
+ function fix_bounds(bounds) {
+ var dx, dy;
+ if (m_clampBoundsX) {
+ if (bounds.right - bounds.left > m_maxBounds.right - m_maxBounds.left) {
+ dx = m_maxBounds.left - ((bounds.right - bounds.left - (
+ m_maxBounds.right - m_maxBounds.left)) / 2) - bounds.left;
+ } else if (bounds.left < m_maxBounds.left) {
+ dx = m_maxBounds.left - bounds.left;
+ } else if (bounds.right > m_maxBounds.right) {
+ dx = m_maxBounds.right - bounds.right;
+ }
+ if (dx) {
+ bounds = {
+ left: bounds.left += dx,
+ right: bounds.right += dx,
+ top: bounds.top,
+ bottom: bounds.bottom
+ };
+ }
}
- if (m_clustering && !val) {
- // Throw out the cluster tree and reset the data
- m_clusterTree = null;
- m_clustering = false;
- s_data(m_allData);
- m_allData = null;
- } else if (!m_clustering && val) {
- // Generate the cluster tree
- m_clustering = true;
- m_this._clusterData();
+ if (m_clampBoundsY) {
+ if (bounds.top - bounds.bottom > m_maxBounds.top - m_maxBounds.bottom) {
+ dy = m_maxBounds.bottom - ((bounds.top - bounds.bottom - (
+ m_maxBounds.top - m_maxBounds.bottom)) / 2) - bounds.bottom;
+ } else if (bounds.top > m_maxBounds.top) {
+ dy = m_maxBounds.top - bounds.top;
+ } else if (bounds.bottom < m_maxBounds.bottom) {
+ dy = m_maxBounds.bottom - bounds.bottom;
+ }
+ if (dy) {
+ bounds = {
+ top: bounds.top += dy,
+ bottom: bounds.bottom += dy,
+ left: bounds.left,
+ right: bounds.right
+ };
+ }
}
- return m_this;
- };
+ return bounds;
+ }
- ////////////////////////////////////////////////////////////////////////////
/**
- * Generate the clustering tree from positions. This might be async in the
- * future.
+ * Call the camera bounds method with the given bounds, but
+ * correct for the viewport aspect ratio.
+ * @private
*/
+ function camera_bounds(bounds) {
+ m_camera.bounds = bounds;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // All the methods are now defined. From here, we are initializing all
+ // internal variables and event handlers.
+ //
////////////////////////////////////////////////////////////////////////////
- this._clusterData = function () {
- if (!m_clustering) {
- // clustering is not enabled, so this is a no-op
- return;
- }
- // set clustering options to default if an options argument wasn't supplied
- var opts = m_clustering === true ? {radius: 0.01} : m_clustering;
+ // Set the world origin
+ m_origin = {x: 0, y: 0};
- // generate the cluster tree from the raw data
- var position = m_this.position();
- m_clusterTree = new geo.util.ClusterGroup(
- opts, this.layer().width(), this.layer().height());
+ // Fix the zoom level (minimum and initial)
+ this.zoomRange(arg);
+ m_zoom = fix_zoom(m_zoom);
+ // Now update to the correct center and zoom level
+ this.center($.extend({}, arg.center || m_center), undefined);
- m_allData.forEach(function (d, i) {
+ this.interactor(arg.interactor || geo.mapInteractor());
+ this.clock(arg.clock || geo.clock());
- // for each point in the data set normalize the coordinate
- // representation and add the point to the cluster treee
- var pt = geo.util.normalizeCoordinates(position(d, i));
- pt.index = i;
- m_clusterTree.addPoint(pt);
- });
+ function resizeSelf() {
+ m_this.resize(0, 0, m_node.width(), m_node.height());
+ }
- // reset the last zoom state and trigger a redraw at the current zoom level
- m_lastZoom = null;
- m_this._handleZoom(m_this.layer().map().zoom());
- };
+ if (arg.autoResize) {
+ $(window).resize(resizeSelf);
+ }
+
+ // attach attribution updates to layer events
+ m_this.geoOn([
+ geo.event.layerAdd,
+ geo.event.layerRemove
+ ], m_this.updateAttribution);
+
+ return this;
+};
+
+/**
+ * General object specification for map types. Any additional
+ * values in the object are passed to the map constructor.
+ * @typedef geo.map.spec
+ * @type {object}
+ * @property {object[]} [data=[]] The default data array to
+ * apply to each feature if none exists
+ * @property {geo.layer.spec[]} [layers=[]] Layers to create
+ */
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Handle zoom events for clustering. This keeps track of the last
- * clustering level, and only regenerates the displayed points when the
- * zoom level changes.
- */
- ////////////////////////////////////////////////////////////////////////////
- this._handleZoom = function (zoom) {
- // get the current zoom level rounded down
- var z = Math.floor(zoom);
+/**
+ * Create a map from an object. Any errors in the creation
+ * of the map will result in returning null.
+ * @param {geo.map.spec} spec The object specification
+ * @returns {geo.map|null}
+ */
+geo.map.create = function (spec) {
+ 'use strict';
- if (!m_clustering || z === m_lastZoom) {
- // short cut when there is nothing to do
- return;
- }
+ var map = geo.map(spec);
- // store the current zoom level privately
- m_lastZoom = z;
+ if (!map) {
+ console.warn('Could not create map.');
+ return null;
+ }
- // get the raw data elements for the points at the current level
- var data = m_clusterTree.points(z).map(function (d) {
- return m_allData[d.index];
- });
+ spec.data = spec.data || [];
+ spec.layers = spec.layers || [];
- // append the clusters at the current level
- m_clusterTree.clusters(z).forEach(function (d) {
- // mark the datum as a cluster for accessor methods
- d.__cluster = true;
+ spec.layers.forEach(function (l) {
+ l.data = l.data || spec.data;
+ l.layer = geo.layer.create(map, l);
+ });
- // store all of the data objects for each point in the cluster as __data
- d.__data = [];
- d.obj.each(function (e) {
- d.__data.push(m_allData[e.index]);
- });
- data.push(d);
- });
+ return map;
+};
- // prevent recomputing the clustering and set the new data array
- m_ignoreData = true;
- m_this.data(data);
- m_this.layer().map().draw(); // replace with m_this.draw() when gl is fixed
- };
+inherit(geo.map, geo.sceneObject);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set position
- *
- * @returns {geo.pointFeature}
- */
- ////////////////////////////////////////////////////////////////////////////
- this.position = function (val) {
- if (val === undefined) {
- return m_this.style("position");
- } else {
- val = geo.util.ensureFunction(val);
- m_this.style("position", function (d, i) {
- if (d.__cluster) {
- return d;
- } else {
- return val(d, i);
- }
- });
- m_this.dataTime().modified();
- m_this.modified();
- }
- return m_this;
- };
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of class feature
+ *
+ * @class
+ * @extends geo.sceneObject
+ * @returns {geo.feature}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.feature = function (arg) {
+ "use strict";
+ if (!(this instanceof geo.feature)) {
+ return new geo.feature(arg);
+ }
+ geo.sceneObject.call(this);
////////////////////////////////////////////////////////////////////////////
/**
- * Update the current range tree object. Should be called whenever the
- * data changes.
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this._updateRangeTree = function () {
- if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) {
- return;
- }
- var pts, position,
- radius = m_this.style.get("radius"),
- stroke = m_this.style.get("stroke"),
- strokeWidth = m_this.style.get("strokeWidth");
-
- position = m_this.position();
-
- m_maxRadius = 0;
-
- // create an array of positions in geo coordinates
- pts = m_this.data().map(function (d, i) {
- var pt = position(d);
- pt.idx = i;
-
- // store the maximum point radius
- m_maxRadius = Math.max(
- m_maxRadius,
- radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0)
- );
-
- return pt;
- });
+ arg = arg || {};
- m_rangeTree = new geo.util.RangeTree(pts);
- m_rangeTreeTime.modified();
- };
+ var m_this = this,
+ s_exit = this._exit,
+ m_selectionAPI = arg.selectionAPI === undefined ? false : arg.selectionAPI,
+ m_style = {},
+ m_layer = arg.layer === undefined ? null : arg.layer,
+ m_gcs = arg.gcs,
+ m_visible = arg.visible === undefined ? true : arg.visible,
+ m_bin = arg.bin === undefined ? 0 : arg.bin,
+ m_renderer = arg.renderer === undefined ? null : arg.renderer,
+ m_dataTime = geo.timestamp(),
+ m_buildTime = geo.timestamp(),
+ m_updateTime = geo.timestamp(),
+ m_selectedFeatures = [];
////////////////////////////////////////////////////////////////////////////
/**
- * Returns an array of datum indices that contain the given point.
- * Largely adapted from wigglemaps pointQuerier:
- *
- * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js
+ * Private method to bind mouse handlers on the map element.
*/
////////////////////////////////////////////////////////////////////////////
- this.pointSearch = function (p) {
- var min, max, data, idx = [], box, found = [], ifound = [], map, pt,
- stroke = m_this.style.get("stroke"),
- strokeWidth = m_this.style.get("strokeWidth"),
- radius = m_this.style.get("radius");
-
- if (!m_this.selectionAPI()) {
- return [];
- }
+ this._bindMouseHandlers = function () {
- data = m_this.data();
- if (!data || !data.length) {
- return {
- found: [],
- index: []
- };
+ // Don't bind handlers for improved performance on features that don't
+ // require it.
+ if (!m_selectionAPI) {
+ return;
}
- map = m_this.layer().map();
- pt = map.gcsToDisplay(p);
-
- // Get the upper right corner in geo coordinates
- min = map.displayToGcs({
- x: pt.x - m_maxRadius,
- y: pt.y + m_maxRadius // GCS coordinates are bottom to top
- });
-
- // Get the lower left corner in geo coordinates
- max = map.displayToGcs({
- x: pt.x + m_maxRadius,
- y: pt.y - m_maxRadius
- });
-
- // Find points inside the bounding box
- box = new geo.util.Box(geo.util.vect(min.x, min.y), geo.util.vect(max.x, max.y));
- m_this._updateRangeTree();
- m_rangeTree.search(box).forEach(function (q) {
- idx.push(q.idx);
- });
-
- // Filter by circular region
- idx.forEach(function (i) {
- var d = data[i],
- p = m_this.position()(d, i),
- dx, dy, rad;
-
- rad = radius(data[i], i);
- rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0;
- p = map.gcsToDisplay(p);
- dx = p.x - pt.x;
- dy = p.y - pt.y;
- if (Math.sqrt(dx * dx + dy * dy) <= rad) {
- found.push(d);
- ifound.push(i);
- }
- });
+ // First unbind to be sure that the handlers aren't bound twice.
+ m_this._unbindMouseHandlers();
- return {
- data: found,
- index: ifound
- };
+ m_this.geoOn(geo.event.mousemove, m_this._handleMousemove);
+ m_this.geoOn(geo.event.mouseclick, m_this._handleMouseclick);
+ m_this.geoOn(geo.event.brushend, m_this._handleBrushend);
+ m_this.geoOn(geo.event.brush, m_this._handleBrush);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Returns an array of datum indices that are contained in the given box.
+ * Private method to unbind mouse handlers on the map element.
*/
////////////////////////////////////////////////////////////////////////////
- this.boxSearch = function (lowerLeft, upperRight) {
- var pos = m_this.position(),
- idx = [];
- // TODO: use the range tree
- m_this.data().forEach(function (d, i) {
- var p = pos(d);
- if (p.x >= lowerLeft.x &&
- p.x <= upperRight.x &&
- p.y >= lowerLeft.y &&
- p.y <= upperRight.y
- ) {
- idx.push(i);
- }
- });
- return idx;
+ this._unbindMouseHandlers = function () {
+ m_this.geoOff(geo.event.mousemove, m_this._handleMousemove);
+ m_this.geoOff(geo.event.mouseclick, m_this._handleMouseclick);
+ m_this.geoOff(geo.event.brushend, m_this._handleBrushend);
+ m_this.geoOff(geo.event.brush, m_this._handleBrush);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Overloaded data method that updates the internal range tree on write.
+ * For binding mouse events, use functions with
+ * the following call signatures:
+ *
+ * function handler(arg) {
+ * // arg.data - the data object of the feature
+ * // arg.index - the index inside the data array of the featue
+ * // arg.mouse - mouse information object (see src/core/mapInteractor.js)
+ * }
+ *
+ * i.e.
+ *
+ * feature.geoOn(geo.event.feature.mousemove, function (arg) {
+ * // do something with the feature marker.
+ * });
*/
////////////////////////////////////////////////////////////////////////////
- this.data = function (data) {
- if (data === undefined) {
- return s_data();
- }
- if (m_clustering && !m_ignoreData) {
- m_allData = data;
- m_this._clusterData();
- } else {
- s_data(data);
- }
- m_ignoreData = false;
- return m_this;
- };
////////////////////////////////////////////////////////////////////////////
/**
- * Returns the bounding box for a given datum in screen coordinates as an
- * object: ::
+ * Search for features containing the given point.
+ *
+ * Returns an object: ::
*
* {
- * min: {
- * x: value,
- * y: value
- * },
- * max: {
- * x: value,
- * y: value
- * }
+ * data: [...] // an array of data objects for matching features
+ * index: [...] // an array of indices of the matching features
* }
*
- * @returns {object}
+ * @argument {Object} coordinate
+ * @returns {Object}
*/
////////////////////////////////////////////////////////////////////////////
- this._boundingBox = function (d) {
- var pt, radius;
-
- // get the position in geo coordinates
- pt = m_this.position()(d);
-
- // convert to screen coordinates
- pt = m_this.layer().map().gcsToDisplay(pt);
-
- // get the radius of the points (should we add stroke width?)
- radius = m_this.style().radius(d);
-
+ this.pointSearch = function () {
+ // base class method does nothing
return {
- min: {
- x: pt.x - radius,
- y: pt.y - radius
- },
- max: {
- x: pt.x + radius,
- y: pt.y + radius
- }
+ index: [],
+ found: []
};
};
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Private mousemove handler
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
-
- var defaultStyle = $.extend(
- {},
- {
- radius: 5.0,
- stroke: true,
- strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
- strokeWidth: 1.25,
- strokeOpacity: 1.0,
- fillColor: { r: 1.0, g: 0.839, b: 0.439 },
- fill: true,
- fillOpacity: 0.8,
- sprites: false,
- sprites_image: null,
- position: function (d) { return d; }
- },
- arg.style === undefined ? {} : arg.style
- );
+ this._handleMousemove = function () {
+ var mouse = m_this.layer().map().interactor().mouse(),
+ data = m_this.data(),
+ over = m_this.pointSearch(mouse.geo),
+ newFeatures = [], oldFeatures = [], lastTop = -1, top = -1;
- if (arg.position !== undefined) {
- defaultStyle.position = arg.position;
+ // Get the index of the element that was previously on top
+ if (m_selectedFeatures.length) {
+ lastTop = m_selectedFeatures[m_selectedFeatures.length - 1];
}
- m_this.style(defaultStyle);
- m_this.dataTime().modified();
+ // There are probably faster ways of doing this:
+ newFeatures = over.index.filter(function (i) {
+ return m_selectedFeatures.indexOf(i) < 0;
+ });
+ oldFeatures = m_selectedFeatures.filter(function (i) {
+ return over.index.indexOf(i) < 0;
+ });
- // bind to the zoom handler for point clustering
- m_this.geoOn(geo.event.zoom, function (evt) {
- m_this._handleZoom(evt.zoomLevel);
+ geo.feature.eventID += 1;
+ // Fire events for mouse in first.
+ newFeatures.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.mouseover, {
+ data: data[i],
+ index: i,
+ mouse: mouse,
+ eventID: geo.feature.eventID,
+ top: idx === newFeatures.length - 1
+ }, true);
});
- };
- return m_this;
-};
+ geo.feature.eventID += 1;
+ // Fire events for mouse out next
+ oldFeatures.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.mouseout, {
+ data: data[i],
+ index: i,
+ mouse: mouse,
+ eventID: geo.feature.eventID,
+ top: idx === oldFeatures.length - 1
+ }, true);
+ });
-geo.event.pointFeature = $.extend({}, geo.event.feature);
+ geo.feature.eventID += 1;
+ // Fire events for mouse move last
+ over.index.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.mousemove, {
+ data: data[i],
+ index: i,
+ mouse: mouse,
+ eventID: geo.feature.eventID,
+ top: idx === over.index.length - 1
+ }, true);
+ });
-/**
- * Object specification for a point feature.
- *
- * @extends geo.feature.spec // need to make a jsdoc plugin for this to work
- * @typedef geo.pointFeature.spec
- * @type {object}
- */
+ // Replace the selected features array
+ m_selectedFeatures = over.index;
-/**
- * Create a pointFeature from an object.
- * @see {@link geo.feature.create}
- * @param {geo.layer} layer The layer to add the feature to
- * @param {geo.pointFeature.spec} spec The object specification
- * @returns {geo.pointFeature|null}
- */
-geo.pointFeature.create = function (layer, renderer, spec) {
- "use strict";
+ // Get the index of the element that is now on top
+ if (m_selectedFeatures.length) {
+ top = m_selectedFeatures[m_selectedFeatures.length - 1];
+ }
- spec.type = "point";
- return geo.feature.create(layer, spec);
-};
+ if (lastTop !== top) {
+ // The element on top changed so we need to fire mouseon/mouseoff
+ if (lastTop !== -1) {
+ m_this.geoTrigger(geo.event.feature.mouseoff, {
+ data: data[lastTop],
+ index: lastTop,
+ mouse: mouse
+ }, true);
+ }
-inherit(geo.pointFeature, geo.feature);
+ if (top !== -1) {
+ m_this.geoTrigger(geo.event.feature.mouseon, {
+ data: data[top],
+ index: top,
+ mouse: mouse
+ }, true);
+ }
+ }
+ };
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class lineFeature
- *
- * @class
- * @extends geo.feature
- * @returns {geo.lineFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.lineFeature = function (arg) {
- "use strict";
- if (!(this instanceof geo.lineFeature)) {
- return new geo.lineFeature(arg);
- }
- arg = arg || {};
- geo.feature.call(this, arg);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Private mouseclick handler
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._handleMouseclick = function () {
+ var mouse = m_this.layer().map().interactor().mouse(),
+ data = m_this.data(),
+ over = m_this.pointSearch(mouse.geo);
+
+ geo.feature.eventID += 1;
+ over.index.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.mouseclick, {
+ data: data[i],
+ index: i,
+ mouse: mouse,
+ eventID: geo.feature.eventID,
+ top: idx === over.index.length - 1
+ }, true);
+ });
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Private brush handler.
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init;
+ this._handleBrush = function (brush) {
+ var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
+ data = m_this.data();
+
+ geo.feature.eventID += 1;
+ idx.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.brush, {
+ data: data[i],
+ index: i,
+ mouse: brush.mouse,
+ brush: brush,
+ eventID: geo.feature.eventID,
+ top: idx === idx.length - 1
+ }, true);
+ });
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set line accessor
- *
- * @returns {geo.pointFeature}
+ * Private brushend handler.
*/
////////////////////////////////////////////////////////////////////////////
- this.line = function (val) {
- if (val === undefined) {
- return m_this.style("line");
- } else {
- m_this.style("line", val);
- m_this.dataTime().modified();
- m_this.modified();
- }
- return m_this;
+ this._handleBrushend = function (brush) {
+ var idx = m_this.boxSearch(brush.gcs.lowerLeft, brush.gcs.upperRight),
+ data = m_this.data();
+
+ geo.feature.eventID += 1;
+ idx.forEach(function (i, idx) {
+ m_this.geoTrigger(geo.event.feature.brushend, {
+ data: data[i],
+ index: i,
+ mouse: brush.mouse,
+ brush: brush,
+ eventID: geo.feature.eventID,
+ top: idx === idx.length - 1
+ }, true);
+ });
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set position accessor
- *
- * @returns {geo.pointFeature}
+ * Get/Set style used by the feature
*/
////////////////////////////////////////////////////////////////////////////
- this.position = function (val) {
- if (val === undefined) {
- return m_this.style("position");
+ this.style = function (arg1, arg2) {
+ if (arg1 === undefined) {
+ return m_style;
+ } else if (typeof arg1 === "string" && arg2 === undefined) {
+ return m_style[arg1];
+ } else if (arg2 === undefined) {
+ m_style = $.extend({}, m_style, arg1);
+ m_this.modified();
+ return m_this;
} else {
- m_this.style("position", val);
- m_this.dataTime().modified();
+ m_style[arg1] = arg2;
m_this.modified();
+ return m_this;
}
- return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Returns an array of datum indices that contain the given point.
- * This is a slow implementation with runtime order of the number of
- * vertices.
+ * A uniform getter that always returns a function even for constant styles.
+ * Maybe extend later to support accessor-like objects. If undefined input,
+ * return all the styles as an object.
+ *
+ * @param {string|undefined} key
+ * @return {function}
*/
////////////////////////////////////////////////////////////////////////////
- this.pointSearch = function (p) {
- var data, pt, map, line, width, indices = [], found = [], pos;
- data = m_this.data();
- if (!data || !data.length) {
- return {
- found: [],
- index: []
- };
- }
-
- map = m_this.layer().map();
- line = m_this.line();
- width = m_this.style.get("strokeWidth");
- pos = m_this.position();
- pt = map.gcsToDisplay(p);
-
- // minimum l2 distance squared from
- // q -> line(u, v)
- function lineDist2(q, u, v) {
- var t, l2 = dist2(u, v);
-
- if (l2 < 1) {
- // u, v are within 1 pixel
- return dist2(q, u);
- }
-
- t = ((q.x - u.x) * (v.x - u.x) + (q.y - u.y) * (v.y - u.y)) / l2;
- if (t < 0) { return dist2(q, u); }
- if (t > 1) { return dist2(q, v); }
- return dist2(
- q,
- {
- x: u.x + t * (v.x - u.x),
- y: u.y + t * (v.y - u.y)
+ this.style.get = function (key) {
+ var tmp, out;
+ if (key === undefined) {
+ var all = {}, k;
+ for (k in m_style) {
+ if (m_style.hasOwnProperty(k)) {
+ all[k] = m_this.style.get(k);
}
- );
- }
-
- // l2 distance squared from u to v
- function dist2(u, v) {
- var dx = u.x - v.x,
- dy = u.y - v.y;
- return dx * dx + dy * dy;
+ }
+ return all;
}
-
- // for each line
- data.forEach(function (d, index) {
- var last = null;
-
- try {
- line(d, index).forEach(function (current, j) {
-
- // get the screen coordinates of the current point
- var p = pos(current, j, d, index);
- var s = map.gcsToDisplay(p);
- var r = Math.ceil(width(p, j, d, index) / 2) + 2;
- r = r * r;
-
- if (last) {
- // test the line segment s -> last
- if (lineDist2(pt, s, last) <= r) {
-
- // short circuit the loop here
- throw "found";
- }
- }
-
- last = s;
- });
- } catch (err) {
- if (err !== "found") {
- throw err;
- }
- found.push(d);
- indices.push(index);
+ if (key.toLowerCase().match(/color$/)) {
+ if (geo.util.isFunction(m_style[key])) {
+ tmp = geo.util.ensureFunction(m_style[key]);
+ out = function () {
+ return geo.util.convertColor(
+ tmp.apply(this, arguments)
+ );
+ };
+ } else {
+ // if the color is not a function, only convert it once
+ out = geo.util.ensureFunction(geo.util.convertColor(m_style[key]));
}
- });
-
- return {
- data: found,
- index: indices
- };
+ } else {
+ out = geo.util.ensureFunction(m_style[key]);
+ }
+ return out;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Returns an array of line indices that are contained in the given box.
+ * Get layer referenced by the feature
*/
////////////////////////////////////////////////////////////////////////////
- this.boxSearch = function (lowerLeft, upperRight, opts) {
- var pos = m_this.position(),
- idx = [],
- line = m_this.line();
-
- opts = opts || {};
- opts.partial = opts.partial || false;
- if (opts.partial) {
- throw "Unimplemented query method.";
- }
-
- m_this.data().forEach(function (d, i) {
- var inside = true;
- line(d, i).forEach(function (e, j) {
- if (!inside) { return; }
- var p = pos(e, j, d, i);
- if (!(p.x >= lowerLeft.x &&
- p.x <= upperRight.x &&
- p.y >= lowerLeft.y &&
- p.y <= upperRight.y)
- ) {
- inside = false;
- }
- });
- if (inside) {
- idx.push(i);
- }
- });
- return idx;
+ this.layer = function () {
+ return m_layer;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Get renderer used by the feature
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
-
- var defaultStyle = $.extend(
- {},
- {
- "strokeWidth": 1.0,
- // Default to gold color for lines
- "strokeColor": { r: 1.0, g: 0.8431372549, b: 0.0 },
- "strokeStyle": "solid",
- "strokeOpacity": 1.0,
- "line": function (d) { return d; },
- "position": function (d) { return d; }
- },
- arg.style === undefined ? {} : arg.style
- );
-
- if (arg.line !== undefined) {
- defaultStyle.line = arg.line;
- }
-
- if (arg.position !== undefined) {
- defaultStyle.position = arg.position;
- }
-
-
- m_this.style(defaultStyle);
-
- m_this.dataTime().modified();
+ this.renderer = function () {
+ return m_renderer;
};
- this._init(arg);
- return this;
-};
-
-/**
- * Create a lineFeature from an object.
- * @see {@link geo.feature.create}
- * @param {geo.layer} layer The layer to add the feature to
- * @param {geo.lineFeature.spec} spec The object specification
- * @returns {geo.lineFeature|null}
- */
-geo.lineFeature.create = function (layer, spec) {
- "use strict";
-
- spec.type = "line";
- return geo.feature.create(layer, spec);
-};
-
-inherit(geo.lineFeature, geo.feature);
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class pathFeature
- *
- * @class
- * @extends geo.feature
- * @returns {geo.pathFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.pathFeature = function (arg) {
- "use strict";
- if (!(this instanceof geo.pathFeature)) {
- return new geo.pathFeature(arg);
- }
- arg = arg || {};
- geo.feature.call(this, arg);
-
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Get/Set projection of the feature
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- m_position = arg.position === undefined ? [] : arg.position,
- s_init = this._init;
+ this.gcs = function (val) {
+ if (val === undefined) {
+ if (m_gcs === undefined && m_renderer) {
+ return m_renderer.layer().map().ingcs();
+ }
+ return m_gcs;
+ } else {
+ m_gcs = val;
+ m_this.modified();
+ return m_this;
+ }
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set positions
+ * Convert from the renderer's input gcs coordinates to display coordinates.
*
- * @returns {geo.pathFeature}
+ * @param {object} c The input coordinate to convert
+ * @param {object} c.x
+ * @param {object} c.y
+ * @param {object} [c.z=0]
+ * @return {object} Display space coordinates
*/
- ////////////////////////////////////////////////////////////////////////////
- this.position = function (val) {
- if (val === undefined) {
- return m_position;
+ this.featureGcsToDisplay = function (c) {
+ var map = m_renderer.layer().map();
+ c = map.gcsToWorld(c, map.ingcs());
+ c = map.worldToDisplay(c);
+ if (m_renderer.baseToLocal) {
+ c = m_renderer.baseToLocal(c);
}
- // Copy incoming array of positions
- m_position = val;
- m_this.dataTime().modified();
- m_this.modified();
- return m_this;
+ return c;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Get/Set visibility of the feature
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
-
- var defaultStyle = $.extend(
- {},
- {
- "strokeWidth": function () { return 1; },
- "strokeColor": function () { return { r: 1.0, g: 1.0, b: 1.0 }; }
- },
- arg.style === undefined ? {} : arg.style
- );
+ this.visible = function (val) {
+ if (val === undefined) {
+ return m_visible;
+ } else {
+ m_visible = val;
+ m_this.modified();
- m_this.style(defaultStyle);
+ // bind or unbind mouse handlers on visibility change
+ if (m_visible) {
+ m_this._bindMouseHandlers();
+ } else {
+ m_this._unbindMouseHandlers();
+ }
- if (m_position) {
- m_this.dataTime().modified();
+ return m_this;
}
};
- this._init(arg);
- return this;
-};
-
-inherit(geo.pathFeature, geo.feature);
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class polygonFeature
- *
- * @class
- * @extends geo.feature
- * @returns {geo.polygonFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.polygonFeature = function (arg) {
- "use strict";
- if (!(this instanceof geo.polygonFeature)) {
- return new geo.polygonFeature(arg);
- }
- arg = arg || {};
- geo.feature.call(this, arg);
-
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Get/Set bin of the feature
+ *
+ * Bin number is typically used for sorting the order of rendering
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- m_position,
- m_polygon,
- s_init = this._init,
- s_data = this.data,
- m_coordinates = {outer: [], inner: []};
-
- if (arg.polygon === undefined) {
- m_polygon = function (d) {
- return d;
- };
- } else {
- m_polygon = arg.polygon;
- }
-
- if (arg.position === undefined) {
- m_position = function (d) {
- return d;
- };
- } else {
- m_position = arg.position;
- }
+ this.bin = function (val) {
+ if (val === undefined) {
+ return m_bin;
+ } else {
+ m_bin = val;
+ m_this.modified();
+ return m_this;
+ }
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Override the parent data method to keep track of changes to the
- * internal coordinates.
+ * Get/Set timestamp of data change
*/
////////////////////////////////////////////////////////////////////////////
- this.data = function (arg) {
- var ret = s_data(arg);
- if (arg !== undefined) {
- getCoordinates();
+ this.dataTime = function (val) {
+ if (val === undefined) {
+ return m_dataTime;
+ } else {
+ m_dataTime = val;
+ m_this.modified();
+ return m_this;
}
- return ret;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get the internal coordinates whenever the data changes. For now, we do
- * the computation in world coordinates, but we will need to work in GCS
- * for other projections.
- * @private
+ * Get/Set timestamp of last time build happened
*/
////////////////////////////////////////////////////////////////////////////
- function getCoordinates() {
- var posFunc = m_this.position(),
- polyFunc = m_this.polygon();
- m_coordinates = m_this.data().map(function (d, i) {
- var poly = polyFunc(d);
- var outer, inner;
-
- outer = (poly.outer || []).map(function (d0, j) {
- return posFunc.call(m_this, d0, j, d, i);
- });
-
- inner = (poly.inner || []).map(function (hole) {
- return (hole || []).map(function (d0, k) {
- return posFunc.call(m_this, d0, k, d, i);
- });
- });
- return {
- outer: outer,
- inner: inner
- };
- });
- }
+ this.buildTime = function (val) {
+ if (val === undefined) {
+ return m_buildTime;
+ } else {
+ m_buildTime = val;
+ m_this.modified();
+ return m_this;
+ }
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set polygon accessor
- *
- * @returns {geo.pointFeature}
+ * Get/Set timestamp of last time update happened
*/
////////////////////////////////////////////////////////////////////////////
- this.polygon = function (val) {
+ this.updateTime = function (val) {
if (val === undefined) {
- return m_polygon;
+ return m_updateTime;
} else {
- m_polygon = val;
- m_this.dataTime().modified();
+ m_updateTime = val;
m_this.modified();
- getCoordinates();
+ return m_this;
}
- return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set position accessor
+ * Get/Set the data array for the feature.
*
- * @returns {geo.pointFeature}
+ * @returns {Array|this}
*/
////////////////////////////////////////////////////////////////////////////
- this.position = function (val) {
- if (val === undefined) {
- return m_position;
+ this.data = function (data) {
+ if (data === undefined) {
+ return m_this.style("data") || [];
} else {
- m_position = val;
+ m_this.style("data", data);
m_this.dataTime().modified();
m_this.modified();
- getCoordinates();
+ return m_this;
}
- return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Point searce method for selection api. Returns markers containing the
- * given point.
- * @argument {Object} coordinate
- * @returns {Object}
+ * Query if the selection API is enabled for this feature.
+ * @returns {bool}
*/
////////////////////////////////////////////////////////////////////////////
- this.pointSearch = function (coordinate) {
- var found = [], indices = [], data = m_this.data();
- m_coordinates.forEach(function (coord, i) {
- var inside = geo.util.pointInPolygon(
- coordinate,
- coord.outer,
- coord.inner
- );
- if (inside) {
- indices.push(i);
- found.push(data[i]);
- }
- });
- return {
- index: indices,
- found: found
- };
+ this.selectionAPI = function () {
+ return m_selectionAPI;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize
+ *
+ * Derived class should implement this
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ if (!m_layer) {
+ throw "Feature requires a valid layer";
+ }
+ m_style = $.extend({},
+ {"opacity": 1.0}, arg.style === undefined ? {} :
+ arg.style);
+ m_this._bindMouseHandlers();
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Build
+ *
+ * Derived class should implement this
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._build = function () {
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Update
+ *
+ * Derived class should implement this
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._update = function () {
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Destroy
+ *
+ * Derived class should implement this
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._exit = function () {
+ m_this._unbindMouseHandlers();
+ m_selectedFeatures = [];
+ m_style = {};
+ arg = {};
+ s_exit();
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Initialize
- */
- ////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
+ this._init(arg);
+ return this;
+};
- var defaultStyle = $.extend(
- {},
- {
- "fillColor": { r: 0.0, g: 0.5, b: 0.5 },
- "fillOpacity": 1.0
- },
- arg.style === undefined ? {} : arg.style
- );
+/**
+ * The most recent feature event triggered.
+ * @type {number}
+ */
+geo.feature.eventID = 0;
- m_this.style(defaultStyle);
+/**
+ * General object specification for feature types.
+ * @typedef geo.feature.spec
+ * @type {object}
+ * @property {string} type A supported feature type.
+ * @property {object[]} [data=[]] An array of arbitrary objects used to
+ * construct the feature. These objects (and their associated
+ * indices in the array) will be passed back to style and attribute
+ * accessors provided by the user. In general the number of
+ * "markers" drawn will be equal to the length of this array.
+ */
- if (m_position) {
- m_this.dataTime().modified();
- }
- };
+/**
+ * Create a feature from an object. The implementation here is
+ * meant to define the general interface of creating features
+ * from a javascript object. See documentation from individual
+ * feature types for specific details. In case of an error in
+ * the arguments this method will return null;
+ * @param {geo.layer} layer The layer to add the feature to
+ * @param {geo.feature.spec} [spec={}] The object specification
+ * @returns {geo.feature|null}
+ */
+geo.feature.create = function (layer, spec) {
+ "use strict";
- this._init(arg);
- return this;
+ var type = spec.type;
+
+ // Check arguments
+ if (!(layer instanceof geo.layer)) {
+ console.warn("Invalid layer");
+ return null;
+ }
+ if (typeof spec !== "object") {
+ console.warn("Invalid spec");
+ return null;
+ }
+ var feature = layer.createFeature(type);
+ if (!feature) {
+ console.warn("Could not create feature type '" + type + "'");
+ return null;
+ }
+
+ spec = spec || {};
+ spec.data = spec.data || [];
+ return feature.style(spec);
};
-inherit(geo.polygonFeature, geo.feature);
+inherit(geo.feature, geo.sceneObject);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class planeFeature
+ * Create a new instance of class pointFeature
*
* @class
- * @extends geo.polygonFeature
- * @returns {geo.planeFeature}
+ * @param {object} arg Options object
+ * @param {boolean} arg.clustering Enable point clustering
+ * @extends geo.feature
+ * @returns {geo.pointFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.planeFeature = function (arg) {
+geo.pointFeature = function (arg) {
"use strict";
- if (!(this instanceof geo.planeFeature)) {
- return new geo.planeFeature(arg);
+ if (!(this instanceof geo.pointFeature)) {
+ return new geo.pointFeature(arg);
}
arg = arg || {};
+ geo.feature.call(this, arg);
- // Defaults
- arg.ul = arg.ul === undefined ? [0.0, 1.0, 0.0] : arg.ul;
- arg.lr = arg.lr === undefined ? [1.0, 0.0, 0.0] : arg.lr;
- arg.depth = arg.depth === undefined ? 0.0 : arg.depth;
-
- geo.polygonFeature.call(this, arg);
-
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
var m_this = this,
- m_origin = [arg.ul.x, arg.lr.y, arg.depth],
- m_upperLeft = [arg.ul.x, arg.ul.y, arg.depth],
- m_lowerRight = [arg.lr.x, arg.lr.y, arg.depth],
- m_defaultDepth = arg.depth,
- m_drawOnAsyncResourceLoad = arg.drawOnAsyncResourceLoad === undefined ?
- true : false,
- s_init = this._init;
+ s_init = this._init,
+ m_rangeTree = null,
+ m_rangeTreeTime = geo.timestamp(),
+ s_data = this.data,
+ m_maxRadius = 0,
+ m_clustering = arg.clustering,
+ m_clusterTree = null,
+ m_allData = [],
+ m_lastZoom = null,
+ m_ignoreData = false; // flag to ignore data() calls made locally
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set origin
+ * Get/Set clustering option
*
- * @returns {geo.planeFeature}
+ * @returns {geo.pointFeature|boolean}
*/
////////////////////////////////////////////////////////////////////////////
- this.origin = function (val) {
+ this.clustering = function (val) {
if (val === undefined) {
- return m_origin;
- } else if (val instanceof Array) {
- if (val.length > 3 || val.length < 2) {
- throw "Origin point requires point in 2 or 3 dimension";
- }
- m_origin = val.slice(0);
- if (m_origin.length === 2) {
- m_origin[2] = m_defaultDepth;
- }
+ return m_clustering;
+ }
+ if (m_clustering && !val) {
+ // Throw out the cluster tree and reset the data
+ m_clusterTree = null;
+ m_clustering = false;
+ s_data(m_allData);
+ m_allData = null;
+ } else if (!m_clustering && val) {
+ // Generate the cluster tree
+ m_clustering = true;
+ m_this._clusterData();
}
- m_this.dataTime().modified();
- m_this.modified();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set pt1
- *
- * @returns {geo.planeFeature}
+ * Generate the clustering tree from positions. This might be async in the
+ * future.
*/
////////////////////////////////////////////////////////////////////////////
- this.upperLeft = function (val) {
- if (val === undefined) {
- return m_upperLeft;
- } else if (val instanceof Array) {
- if (val.length > 3 || val.length < 2) {
- throw "Upper left point requires point in 2 or 3 dimension";
- }
- m_upperLeft = val.slice(0);
- if (m_upperLeft.length === 2) {
- m_upperLeft[2] = m_defaultDepth;
- }
+ this._clusterData = function () {
+ if (!m_clustering) {
+ // clustering is not enabled, so this is a no-op
+ return;
}
- m_this.dataTime().modified();
- m_this.modified();
- return m_this;
+
+ // set clustering options to default if an options argument wasn't supplied
+ var opts = m_clustering === true ? {radius: 0.01} : m_clustering;
+
+ // generate the cluster tree from the raw data
+ var position = m_this.position();
+ m_clusterTree = new geo.util.ClusterGroup(
+ opts, m_this.layer().width(), m_this.layer().height());
+
+ m_allData.forEach(function (d, i) {
+
+ // for each point in the data set normalize the coordinate
+ // representation and add the point to the cluster treee
+ var pt = geo.util.normalizeCoordinates(position(d, i));
+ pt.index = i;
+ m_clusterTree.addPoint(pt);
+ });
+
+ // reset the last zoom state and trigger a redraw at the current zoom level
+ m_lastZoom = null;
+ m_this._handleZoom(m_this.layer().map().zoom());
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set origin
+ * Handle zoom events for clustering. This keeps track of the last
+ * clustering level, and only regenerates the displayed points when the
+ * zoom level changes.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._handleZoom = function (zoom) {
+ // get the current zoom level rounded down
+ var z = Math.floor(zoom);
+
+ if (!m_clustering || z === m_lastZoom) {
+ // short cut when there is nothing to do
+ return;
+ }
+
+ // store the current zoom level privately
+ m_lastZoom = z;
+
+ // get the raw data elements for the points at the current level
+ var data = m_clusterTree.points(z).map(function (d) {
+ return m_allData[d.index];
+ });
+
+ // append the clusters at the current level
+ m_clusterTree.clusters(z).forEach(function (d) {
+ // mark the datum as a cluster for accessor methods
+ d.__cluster = true;
+
+ // store all of the data objects for each point in the cluster as __data
+ d.__data = [];
+ d.obj.each(function (e) {
+ d.__data.push(m_allData[e.index]);
+ });
+ data.push(d);
+ });
+
+ // prevent recomputing the clustering and set the new data array
+ m_ignoreData = true;
+ m_this.data(data);
+ m_this.layer().map().draw(); // replace with m_this.draw() when gl is fixed
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set position
*
- * @returns {geo.planeFeature}
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this.lowerRight = function (val) {
+ this.position = function (val) {
if (val === undefined) {
- return m_lowerRight;
- } else if (val instanceof Array) {
- if (val.length > 3 || val.length < 2) {
- throw "Lower right point requires point in 2 or 3 dimension";
- }
- m_lowerRight = val.slice(0);
- if (m_lowerRight.length === 2) {
- m_lowerRight[2] = m_defaultDepth;
- }
+ return m_this.style("position");
+ } else {
+ val = geo.util.ensureFunction(val);
+ m_this.style("position", function (d, i) {
+ if (d.__cluster) {
+ return d;
+ } else {
+ return val(d, i);
+ }
+ });
m_this.dataTime().modified();
+ m_this.modified();
}
- m_this.dataTime().modified();
- m_this.modified();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set if draw should happen as soon as a async resource is loaded
+ * Update the current range tree object. Should be called whenever the
+ * data changes.
*/
////////////////////////////////////////////////////////////////////////////
- this.drawOnAsyncResourceLoad = function (val) {
- if (val === undefined) {
- return m_drawOnAsyncResourceLoad;
- } else {
- m_drawOnAsyncResourceLoad = val;
- return m_this;
+ this._updateRangeTree = function () {
+ if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) {
+ return;
}
+ var pts, position,
+ radius = m_this.style.get("radius"),
+ stroke = m_this.style.get("stroke"),
+ strokeWidth = m_this.style.get("strokeWidth");
+
+ position = m_this.position();
+
+ m_maxRadius = 0;
+
+ // create an array of positions in geo coordinates
+ pts = m_this.data().map(function (d, i) {
+ var pt = position(d);
+ pt.idx = i;
+
+ // store the maximum point radius
+ m_maxRadius = Math.max(
+ m_maxRadius,
+ radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0)
+ );
+
+ return pt;
+ });
+
+ m_rangeTree = new geo.util.RangeTree(pts);
+ m_rangeTreeTime.modified();
};
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Returns an array of datum indices that contain the given point.
+ * Largely adapted from wigglemaps pointQuerier:
+ *
+ * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- var style = null;
- s_init.call(m_this, arg);
- style = m_this.style();
- if (style.image === undefined) {
- style.image = null;
+ this.pointSearch = function (p) {
+ var min, max, data, idx = [], box, found = [], ifound = [], map, pt,
+ stroke = m_this.style.get("stroke"),
+ strokeWidth = m_this.style.get("strokeWidth"),
+ radius = m_this.style.get("radius");
+
+ if (!m_this.selectionAPI()) {
+ return [];
}
- m_this.style(style);
- };
- this._init(arg);
- return this;
-};
+ data = m_this.data();
+ if (!data || !data.length) {
+ return {
+ found: [],
+ index: []
+ };
+ }
-inherit(geo.planeFeature, geo.polygonFeature);
+ map = m_this.layer().map();
+ pt = map.gcsToDisplay(p);
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class vectorFeature
- *
- * @class
- * @extends geo.feature
- * @returns {geo.vectorFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.vectorFeature = function (arg) {
- 'use strict';
- if (!(this instanceof geo.vectorFeature)) {
- return new geo.vectorFeature(arg);
- }
- arg = arg || {};
- geo.feature.call(this, arg);
+ // Get the upper right corner in geo coordinates
+ min = map.displayToGcs({
+ x: pt.x - m_maxRadius,
+ y: pt.y + m_maxRadius // GCS coordinates are bottom to top
+ });
+
+ // Get the lower left corner in geo coordinates
+ max = map.displayToGcs({
+ x: pt.x + m_maxRadius,
+ y: pt.y - m_maxRadius
+ });
+
+ // Find points inside the bounding box
+ box = new geo.util.Box(geo.util.vect(min.x, min.y), geo.util.vect(max.x, max.y));
+ m_this._updateRangeTree();
+ m_rangeTree.search(box).forEach(function (q) {
+ idx.push(q.idx);
+ });
+
+ // Filter by circular region
+ idx.forEach(function (i) {
+ var d = data[i],
+ p = m_this.position()(d, i),
+ dx, dy, rad;
+
+ rad = radius(data[i], i);
+ rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0;
+ p = map.gcsToDisplay(p);
+ dx = p.x - pt.x;
+ dy = p.y - pt.y;
+ if (Math.sqrt(dx * dx + dy * dy) <= rad) {
+ found.push(d);
+ ifound.push(i);
+ }
+ });
+
+ return {
+ data: found,
+ index: ifound
+ };
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Returns an array of datum indices that are contained in the given box.
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init,
- s_style = this.style;
+ this.boxSearch = function (lowerLeft, upperRight) {
+ var pos = m_this.position(),
+ idx = [];
+ // TODO: use the range tree
+ m_this.data().forEach(function (d, i) {
+ var p = pos(d);
+ if (p.x >= lowerLeft.x &&
+ p.x <= upperRight.x &&
+ p.y >= lowerLeft.y &&
+ p.y <= upperRight.y
+ ) {
+ idx.push(i);
+ }
+ });
+ return idx;
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Get or set the accessor for the origin of the vector. This is the point
- * that the vector base resides at. Defaults to (0, 0, 0).
- * @param {geo.accessor|geo.geoPosition} [accessor] The origin accessor
+ * Overloaded data method that updates the internal range tree on write.
*/
////////////////////////////////////////////////////////////////////////////
- this.origin = function (val) {
- if (val === undefined) {
- return s_style('origin');
+ this.data = function (data) {
+ if (data === undefined) {
+ return s_data();
+ }
+ if (m_clustering && !m_ignoreData) {
+ m_allData = data;
+ m_this._clusterData();
} else {
- s_style('origin', val);
- m_this.dataTime().modified();
- m_this.modified();
+ s_data(data);
}
+ m_ignoreData = false;
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get or set the accessor for the displacement (coordinates) of the vector.
- * @param {geo.accessor|geo.geoPosition} [accessor] The accessor
+ * Returns the bounding box for a given datum in screen coordinates as an
+ * object: ::
+ *
+ * {
+ * min: {
+ * x: value,
+ * y: value
+ * },
+ * max: {
+ * x: value,
+ * y: value
+ * }
+ * }
+ *
+ * @returns {object}
*/
////////////////////////////////////////////////////////////////////////////
- this.delta = function (val) {
- if (val === undefined) {
- return s_style('delta');
- } else {
- s_style('delta', val);
- m_this.dataTime().modified();
- m_this.modified();
- }
- return m_this;
+ this._boundingBox = function (d) {
+ var pt, radius;
+
+ // get the position in geo coordinates
+ pt = m_this.position()(d);
+
+ // convert to screen coordinates
+ pt = m_this.layer().map().gcsToDisplay(pt);
+
+ // get the radius of the points (should we add stroke width?)
+ radius = m_this.style().radius(d);
+
+ return {
+ min: {
+ x: pt.x - radius,
+ y: pt.y - radius
+ },
+ max: {
+ x: pt.x + radius,
+ y: pt.y + radius
+ }
+ };
};
////////////////////////////////////////////////////////////////////////////
/**
* Initialize
- * @protected
*/
////////////////////////////////////////////////////////////////////////////
this._init = function (arg) {
@@ -20248,75 +22606,76 @@ geo.vectorFeature = function (arg) {
var defaultStyle = $.extend(
{},
{
- strokeColor: 'black',
- strokeWidth: 2.0,
+ radius: 5.0,
+ stroke: true,
+ strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
+ strokeWidth: 1.25,
strokeOpacity: 1.0,
- // TODO: define styles for the end markers
- // originStyle: 'none',
- // endStyle: 'arrow',
- origin: {x: 0, y: 0, z: 0},
- delta: function (d) { return d; },
- scale: null // size scaling factor (null -> renderer decides)
+ fillColor: { r: 1.0, g: 0.839, b: 0.439 },
+ fill: true,
+ fillOpacity: 0.8,
+ sprites: false,
+ sprites_image: null,
+ position: function (d) { return d; }
},
arg.style === undefined ? {} : arg.style
);
- if (arg.origin !== undefined) {
- defaultStyle.origin = arg.origin;
+ if (arg.position !== undefined) {
+ defaultStyle.position = arg.position;
}
m_this.style(defaultStyle);
m_this.dataTime().modified();
+
+ // bind to the zoom handler for point clustering
+ m_this.geoOn(geo.event.zoom, function (evt) {
+ m_this._handleZoom(evt.zoomLevel);
+ });
};
+
+ return m_this;
};
-inherit(geo.vectorFeature, geo.feature);
+geo.event.pointFeature = $.extend({}, geo.event.feature);
-//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class geomFeature
+ * Object specification for a point feature.
*
- * @class
- * @extends geo.feature
- * @returns {geo.geomFeature}
+ * @extends geo.feature.spec // need to make a jsdoc plugin for this to work
+ * @typedef geo.pointFeature.spec
+ * @type {object}
*/
-//////////////////////////////////////////////////////////////////////////////
-geo.geomFeature = function (arg) {
- "use strict";
- if (!(this instanceof geo.geomFeature)) {
- return new geo.geomFeature(arg);
- }
- arg = arg || {};
- geo.feature.call(this, arg);
- arg.style = arg.style === undefined ? $.extend({}, {
- "color": [1.0, 1.0, 1.0],
- "point_sprites": false,
- "point_sprites_image": null
- }, arg.style) : arg.style;
-
- // Update style
- this.style(arg.style);
+/**
+ * Create a pointFeature from an object.
+ * @see {@link geo.feature.create}
+ * @param {geo.layer} layer The layer to add the feature to
+ * @param {geo.pointFeature.spec} spec The object specification
+ * @returns {geo.pointFeature|null}
+ */
+geo.pointFeature.create = function (layer, renderer, spec) {
+ "use strict";
- return this;
+ spec.type = "point";
+ return geo.feature.create(layer, spec);
};
-inherit(geo.geomFeature, geo.feature);
+inherit(geo.pointFeature, geo.feature);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class graphFeature
+ * Create a new instance of class lineFeature
*
* @class
* @extends geo.feature
- * @returns {geo.graphFeature}
+ * @returns {geo.lineFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.graphFeature = function (arg) {
+geo.lineFeature = function (arg) {
"use strict";
-
- if (!(this instanceof geo.graphFeature)) {
- return new geo.graphFeature(arg);
+ if (!(this instanceof geo.lineFeature)) {
+ return new geo.lineFeature(arg);
}
arg = arg || {};
geo.feature.call(this, arg);
@@ -20327,222 +22686,314 @@ geo.graphFeature = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
var m_this = this,
- s_draw = this.draw,
- s_style = this.style,
- m_nodes = null,
- m_points = null,
- m_children = function (d) { return d.children; },
- m_links = [],
- s_init = this._init,
- s_exit = this._exit;
+ s_init = this._init;
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Get/Set line accessor
+ *
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
-
- var defaultStyle = $.extend(true, {},
- {
- nodes: {
- radius: 5.0,
- fill: true,
- fillColor: { r: 1.0, g: 0.0, b: 0.0 },
- strokeColor: { r: 0, g: 0, b: 0 }
- },
- links: {
- strokeColor: { r: 0.0, g: 0.0, b: 0.0 }
- },
- linkType: "path" /* 'path' || 'line' */
- },
- arg.style === undefined ? {} : arg.style
- );
-
- m_this.style(defaultStyle);
- m_this.nodes(function (d) { return d; });
+ this.line = function (val) {
+ if (val === undefined) {
+ return m_this.style("line");
+ } else {
+ m_this.style("line", val);
+ m_this.dataTime().modified();
+ m_this.modified();
+ }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Call child _build methods
+ * Get/Set position accessor
+ *
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this._build = function () {
- m_this.children().forEach(function (child) {
- child._build();
- });
+ this.position = function (val) {
+ if (val === undefined) {
+ return m_this.style("position");
+ } else {
+ m_this.style("position", val);
+ m_this.dataTime().modified();
+ m_this.modified();
+ }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Call child _update methods
+ * Returns an array of datum indices that contain the given point.
+ * This is a slow implementation with runtime order of the number of
+ * vertices.
*/
////////////////////////////////////////////////////////////////////////////
- this._update = function () {
- m_this.children().forEach(function (child) {
- child._update();
- });
- };
+ this.pointSearch = function (p) {
+ var data, pt, map, line, width, indices = [], found = [], pos;
+ data = m_this.data();
+ if (!data || !data.length) {
+ return {
+ found: [],
+ index: []
+ };
+ }
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Custom _exit method to remove all sub-features
- */
- ////////////////////////////////////////////////////////////////////////////
- this._exit = function () {
- m_this.data([]);
- m_links.forEach(function (link) {
- link._exit();
- m_this.removeChild(link);
- });
- m_links = [];
- m_points._exit();
- m_this.removeChild(m_points);
- s_exit();
- return m_this;
- };
+ map = m_this.layer().map();
+ line = m_this.line();
+ width = m_this.style.get("strokeWidth");
+ pos = m_this.position();
+ pt = m_this.featureGcsToDisplay(p);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set style
- */
- ////////////////////////////////////////////////////////////////////////////
- this.style = function (arg, arg2) {
- var out = s_style.call(m_this, arg, arg2);
- if (out !== m_this) {
- return out;
+ // minimum l2 distance squared from
+ // q -> line(u, v)
+ function lineDist2(q, u, v) {
+ var t, l2 = dist2(u, v);
+
+ if (l2 < 1) {
+ // u, v are within 1 pixel
+ return dist2(q, u);
+ }
+
+ t = ((q.x - u.x) * (v.x - u.x) + (q.y - u.y) * (v.y - u.y)) / l2;
+ if (t < 0) { return dist2(q, u); }
+ if (t > 1) { return dist2(q, v); }
+ return dist2(
+ q,
+ {
+ x: u.x + t * (v.x - u.x),
+ y: u.y + t * (v.y - u.y)
+ }
+ );
}
- // set styles for sub-features
- m_points.style(arg.nodes);
- m_links.forEach(function (l) {
- l.style(arg.links);
+
+ // l2 distance squared from u to v
+ function dist2(u, v) {
+ var dx = u.x - v.x,
+ dy = u.y - v.y;
+ return dx * dx + dy * dy;
+ }
+
+ // for each line
+ data.forEach(function (d, index) {
+ var last = null;
+
+ try {
+ line(d, index).forEach(function (current, j) {
+
+ // get the screen coordinates of the current point
+ var p = pos(current, j, d, index);
+ var s = m_this.featureGcsToDisplay(p);
+ var r = Math.ceil(width(p, j, d, index) / 2) + 2;
+ r = r * r;
+
+ if (last) {
+ // test the line segment s -> last
+ if (lineDist2(pt, s, last) <= r) {
+
+ // short circuit the loop here
+ throw "found";
+ }
+ }
+
+ last = s;
+ });
+ } catch (err) {
+ if (err !== "found") {
+ throw err;
+ }
+ found.push(d);
+ indices.push(index);
+ }
});
- return m_this;
+
+ return {
+ data: found,
+ index: indices
+ };
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set links accessor.
+ * Returns an array of line indices that are contained in the given box.
*/
////////////////////////////////////////////////////////////////////////////
- this.links = function (arg) {
- if (arg === undefined) {
- return m_children;
+ this.boxSearch = function (lowerLeft, upperRight, opts) {
+ var pos = m_this.position(),
+ idx = [],
+ line = m_this.line();
+
+ opts = opts || {};
+ opts.partial = opts.partial || false;
+ if (opts.partial) {
+ throw "Unimplemented query method.";
}
- m_children = geo.util.ensureFunction(arg);
- return m_this;
+ m_this.data().forEach(function (d, i) {
+ var inside = true;
+ line(d, i).forEach(function (e, j) {
+ if (!inside) { return; }
+ var p = pos(e, j, d, i);
+ if (!(p.x >= lowerLeft.x &&
+ p.x <= upperRight.x &&
+ p.y >= lowerLeft.y &&
+ p.y <= upperRight.y)
+ ) {
+ inside = false;
+ }
+ });
+ if (inside) {
+ idx.push(i);
+ }
+ });
+ return idx;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set nodes
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this.nodes = function (val) {
- if (val === undefined) {
- return m_nodes;
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+
+ var defaultStyle = $.extend(
+ {},
+ {
+ "strokeWidth": 1.0,
+ // Default to gold color for lines
+ "strokeColor": { r: 1.0, g: 0.8431372549, b: 0.0 },
+ "strokeStyle": "solid",
+ "strokeOpacity": 1.0,
+ "line": function (d) { return d; },
+ "position": function (d) { return d; }
+ },
+ arg.style === undefined ? {} : arg.style
+ );
+
+ if (arg.line !== undefined) {
+ defaultStyle.line = arg.line;
}
- m_nodes = val;
- m_this.modified();
- return m_this;
+
+ if (arg.position !== undefined) {
+ defaultStyle.position = arg.position;
+ }
+
+
+ m_this.style(defaultStyle);
+
+ m_this.dataTime().modified();
};
+ this._init(arg);
+ return this;
+};
+
+/**
+ * Create a lineFeature from an object.
+ * @see {@link geo.feature.create}
+ * @param {geo.layer} layer The layer to add the feature to
+ * @param {geo.lineFeature.spec} spec The object specification
+ * @returns {geo.lineFeature|null}
+ */
+geo.lineFeature.create = function (layer, spec) {
+ "use strict";
+
+ spec.type = "line";
+ return geo.feature.create(layer, spec);
+};
+
+inherit(geo.lineFeature, geo.feature);
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of class pathFeature
+ *
+ * @class
+ * @extends geo.feature
+ * @returns {geo.pathFeature}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.pathFeature = function (arg) {
+ "use strict";
+ if (!(this instanceof geo.pathFeature)) {
+ return new geo.pathFeature(arg);
+ }
+ arg = arg || {};
+ geo.feature.call(this, arg);
+
////////////////////////////////////////////////////////////////////////////
/**
- * Get internal node feature
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this.nodeFeature = function () {
- return m_points;
- };
+ var m_this = this,
+ m_position = arg.position === undefined ? [] : arg.position,
+ s_init = this._init;
////////////////////////////////////////////////////////////////////////////
/**
- * Get internal link features
+ * Get/Set positions
+ *
+ * @returns {geo.pathFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this.linkFeatures = function () {
- return m_links;
+ this.position = function (val) {
+ if (val === undefined) {
+ return m_position;
+ }
+ // Copy incoming array of positions
+ m_position = val;
+ m_this.dataTime().modified();
+ m_this.modified();
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Build the feature for drawing
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this.draw = function () {
-
- var layer = m_this.layer(),
- data = m_this.data(),
- nLinks = 0,
- style;
-
- // get the feature style object
- style = m_this.style();
-
- // Bind data to the point nodes
- m_points.data(data);
- m_points.style(style.nodes);
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
- // get links from node connections
- data.forEach(function (source) {
- (source.children || []).forEach(function (target) {
- var link;
- nLinks += 1;
- if (m_links.length < nLinks) {
- link = geo.createFeature(
- style.linkType, layer, layer.renderer()
- ).style(style.links);
- m_this.addChild(link);
- m_links.push(link);
- }
- m_links[nLinks - 1].data([source, target]);
- });
- });
+ var defaultStyle = $.extend(
+ {},
+ {
+ "strokeWidth": function () { return 1; },
+ "strokeColor": function () { return { r: 1.0, g: 1.0, b: 1.0 }; }
+ },
+ arg.style === undefined ? {} : arg.style
+ );
- m_links.splice(nLinks, m_links.length - nLinks).forEach(function (l) {
- l._exit();
- m_this.removeChild(l);
- });
+ m_this.style(defaultStyle);
- s_draw();
- return m_this;
+ if (m_position) {
+ m_this.dataTime().modified();
+ }
};
- m_points = geo.createFeature(
- "point",
- this.layer(),
- this.layer().renderer()
- );
- m_this.addChild(m_points);
-
- if (arg.nodes) {
- this.nodes(arg.nodes);
- }
-
this._init(arg);
return this;
};
-inherit(geo.graphFeature, geo.feature);
+inherit(geo.pathFeature, geo.feature);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class contourFeature
+ * Create a new instance of class polygonFeature
*
* @class
* @extends geo.feature
- * @returns {geo.contourFeature}
- *
+ * @returns {geo.polygonFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.contourFeature = function (arg) {
- 'use strict';
- if (!(this instanceof geo.contourFeature)) {
- return new geo.contourFeature(arg);
+geo.polygonFeature = function (arg) {
+ "use strict";
+ if (!(this instanceof geo.polygonFeature)) {
+ return new geo.polygonFeature(arg);
}
arg = arg || {};
geo.feature.call(this, arg);
@@ -20553,16 +23004,26 @@ geo.contourFeature = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
var m_this = this,
- m_contour = {},
+ m_position,
+ m_polygon,
s_init = this._init,
- s_data = this.data;
+ s_data = this.data,
+ m_coordinates = {outer: [], inner: []};
- if (arg.contour === undefined) {
- m_contour = function (d) {
+ if (arg.polygon === undefined) {
+ m_polygon = function (d) {
return d;
};
} else {
- m_contour = arg.contour;
+ m_polygon = arg.polygon;
+ }
+
+ if (arg.position === undefined) {
+ m_position = function (d) {
+ return d;
+ };
+ } else {
+ m_position = arg.position;
}
////////////////////////////////////////////////////////////////////////////
@@ -20573,87 +23034,60 @@ geo.contourFeature = function (arg) {
////////////////////////////////////////////////////////////////////////////
this.data = function (arg) {
var ret = s_data(arg);
+ if (arg !== undefined) {
+ getCoordinates();
+ }
return ret;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set contour accessor
- *
- * @returns {geo.pointFeature}
+ * Get the internal coordinates whenever the data changes. For now, we do
+ * the computation in world coordinates, but we will need to work in GCS
+ * for other projections.
+ * @private
*/
- ////////////////////////////////////////////////////////////////////////////
- this.contour = function (arg1, arg2) {
- if (arg1 === undefined) {
- return m_contour;
- }
- if (typeof arg1 === 'string' && arg2 === undefined) {
- return m_contour[arg1];
- }
- if (arg2 === undefined) {
- var contour = $.extend(
- {},
- {
- gridWidth: function () {
- if (arg1.gridHeight) {
- return Math.floor(m_this.data().length / arg1.gridHeight);
- }
- return Math.floor(Math.sqrt(m_this.data().length));
- },
- gridHeight: function () {
- if (arg1.gridWidth) {
- return Math.floor(m_this.data().length / arg1.gridWidth);
- }
- return Math.floor(Math.sqrt(m_this.data().length));
- },
- minColor: 'black',
- minOpacity: 0,
- maxColor: 'black',
- maxOpacity: 0,
- /* 9-step based on paraview bwr colortable */
- colorRange: [
- {r: 0.07514311, g: 0.468049805, b: 1},
- {r: 0.468487184, g: 0.588057293, b: 1},
- {r: 0.656658579, g: 0.707001303, b: 1},
- {r: 0.821573924, g: 0.837809045, b: 1},
- {r: 0.943467973, g: 0.943498599, b: 0.943398095},
- {r: 1, g: 0.788626485, b: 0.750707739},
- {r: 1, g: 0.6289553, b: 0.568237474},
- {r: 1, g: 0.472800903, b: 0.404551679},
- {r: 0.916482116, g: 0.236630659, b: 0.209939162}
- ]
- },
- m_contour,
- arg1
- );
- m_contour = contour;
- } else {
- m_contour[arg1] = arg2;
- }
- m_this.modified();
- return m_this;
- };
+ ////////////////////////////////////////////////////////////////////////////
+ function getCoordinates() {
+ var posFunc = m_this.position(),
+ polyFunc = m_this.polygon();
+ m_coordinates = m_this.data().map(function (d, i) {
+ var poly = polyFunc(d);
+ var outer, inner;
+
+ outer = (poly.outer || []).map(function (d0, j) {
+ return posFunc.call(m_this, d0, j, d, i);
+ });
+
+ inner = (poly.inner || []).map(function (hole) {
+ return (hole || []).map(function (d0, k) {
+ return posFunc.call(m_this, d0, k, d, i);
+ });
+ });
+ return {
+ outer: outer,
+ inner: inner
+ };
+ });
+ }
////////////////////////////////////////////////////////////////////////////
/**
- * A uniform getter that always returns a function even for constant values.
- * If undefined input, return all the contour values as an object.
+ * Get/Set polygon accessor
*
- * @param {string|undefined} key
- * @return {function}
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this.contour.get = function (key) {
- if (key === undefined) {
- var all = {}, k;
- for (k in m_contour) {
- if (m_contour.hasOwnProperty(k)) {
- all[k] = m_this.contour.get(k);
- }
- }
- return all;
+ this.polygon = function (val) {
+ if (val === undefined) {
+ return m_polygon;
+ } else {
+ m_polygon = val;
+ m_this.dataTime().modified();
+ m_this.modified();
+ getCoordinates();
}
- return geo.util.ensureFunction(m_contour[key]);
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
@@ -20665,220 +23099,41 @@ geo.contourFeature = function (arg) {
////////////////////////////////////////////////////////////////////////////
this.position = function (val) {
if (val === undefined) {
- return m_this.style('position');
+ return m_position;
} else {
- m_this.style('position', val);
+ m_position = val;
m_this.dataTime().modified();
m_this.modified();
+ getCoordinates();
}
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Create a set of vertices, values at the vertices, and opacities at the
- * vertices. Create a set of triangles of indices into the vertex array.
- * Create a color and opacity map corresponding to the values.
- *
- * @returns: an object with pos, value, opacity, elements, minValue,
- * maxValue, minColor, maxColor, colorMap, factor. If there is no
- * contour data that can be used, only elements is guaranteed to
- * exist, and it will be a zero-length array.
- */
- ////////////////////////////////////////////////////////////////////////////
- this.createContours = function () {
- var i, i3, j, idx, k, val, numPts, usedPts = 0, usePos, item,
- idxMap = {},
- minval, maxval, range,
- contour = m_this.contour,
- data = m_this.data(),
- posFunc = m_this.position(), posVal,
- gridW = contour.get('gridWidth')(),
- gridH = contour.get('gridHeight')(),
- x0 = contour.get('x0')(),
- y0 = contour.get('y0')(),
- dx = contour.get('dx')(),
- dy = contour.get('dy')(),
- opacityFunc = m_this.style.get('opacity'),
- opacityRange = contour.get('opacityRange')(),
- rangeValues = contour.get('rangeValues')(),
- valueFunc = m_this.style.get('value'), values = [],
- stepped = contour.get('stepped')(),
- wrapLong = contour.get('wrapLongitude')(),
- calcX, skipColumn, x, origI, /* used for wrapping */
- gridWorig = gridW, /* can be different when wrapping */
- result = {
- minValue: contour.get('min')(),
- maxValue: contour.get('max')(),
- stepped: stepped === undefined || stepped ? true : false,
- wrapLongitude: wrapLong === undefined || wrapLong ? true : false,
- colorMap: [],
- elements: []
- };
- /* Create the min/max colors and the color array */
- result.minColor = $.extend({a: contour.get('minOpacity')() || 0},
- geo.util.convertColor(contour.get('minColor')()));
- result.maxColor = $.extend({a: contour.get('maxOpacity')() || 0},
- geo.util.convertColor(contour.get('maxColor')()));
- contour.get('colorRange')().forEach(function (clr, idx) {
- result.colorMap.push($.extend(
- {a: opacityRange && opacityRange[idx] !== undefined ?
- opacityRange[idx] : 1}, geo.util.convertColor(clr)));
- });
- /* Determine which values are usable */
- if (gridW * gridH > data.length) {
- gridH = Math.floor(data.length) / gridW;
- }
- /* If we are not using the position values (we are using x0, y0, dx, dy),
- * and wrapLongitude is turned on, and the position spans 180 degrees,
- * duplicate one or two columns of points at opposite ends of the map. */
- usePos = (x0 === null || x0 === undefined || y0 === null ||
- y0 === undefined || !dx || !dy);
- if (!usePos && result.wrapLongitude && (x0 < -180 || x0 > 180 ||
- x0 + dx * (gridW - 1) < -180 || x0 + dx * (gridW - 1) > 180) &&
- dx > -180 && dx < 180) {
- calcX = [];
- for (i = 0; i < gridW; i += 1) {
- x = x0 + i * dx;
- while (x < -180) { x += 360; }
- while (x > 180) { x -= 360; }
- if (i && Math.abs(x - calcX[calcX.length - 1]) > 180) {
- if (x > calcX[calcX.length - 1]) {
- calcX.push(x - 360);
- calcX.push(calcX[calcX.length - 2] + 360);
- } else {
- calcX.push(x + 360);
- calcX.push(calcX[calcX.length - 2] - 360);
- }
- skipColumn = i;
- }
- calcX.push(x);
- }
- gridW += 2;
- if (Math.abs(Math.abs(gridWorig * dx) - 360) < 0.01) {
- gridW += 1;
- x = x0 + gridWorig * dx;
- while (x < -180) { x += 360; }
- while (x > 180) { x -= 360; }
- calcX.push(x);
- }
- }
- /* Calculate the value for point */
- numPts = gridW * gridH;
- for (i = 0; i < numPts; i += 1) {
- if (skipColumn === undefined) {
- val = parseFloat(valueFunc(data[i]));
- } else {
- j = Math.floor(i / gridW);
- origI = i - j * gridW;
- origI += (origI > skipColumn ? -2 : 0);
- if (origI >= gridWorig) {
- origI -= gridWorig;
- }
- origI += j * gridWorig;
- val = parseFloat(valueFunc(data[origI]));
- }
- values[i] = isNaN(val) ? null : val;
- if (values[i] !== null) {
- idxMap[i] = usedPts;
- usedPts += 1;
- if (minval === undefined) {
- minval = maxval = values[i];
- }
- if (values[i] < minval) {
- minval = values[i];
- }
- if (values[i] > maxval) {
- maxval = values[i];
- }
- }
- }
- if (!usedPts) {
- return result;
- }
- if (!$.isNumeric(result.minValue)) {
- result.minValue = minval;
- }
- if (!$.isNumeric(result.maxValue)) {
- result.maxValue = maxval;
- }
- if (!rangeValues || rangeValues.length !== result.colorMap.length + 1) {
- rangeValues = null;
- }
- if (rangeValues) { /* ensure increasing monotonicity */
- for (k = 1; k < rangeValues.length; k += 1) {
- if (rangeValues[k] > rangeValues[k + 1]) {
- rangeValues = null;
- break;
- }
- }
- }
- if (rangeValues) {
- result.minValue = rangeValues[0];
- result.maxValue = rangeValues[rangeValues.length - 1];
- }
- range = result.maxValue - result.minValue;
- if (!range) {
- result.colorMap = result.colorMap.slice(0, 1);
- range = 1;
- rangeValues = null;
- }
- result.rangeValues = rangeValues;
- result.factor = result.colorMap.length / range;
- /* Create triangles */
- for (j = idx = 0; j < gridH - 1; j += 1, idx += 1) {
- for (i = 0; i < gridW - 1; i += 1, idx += 1) {
- if (values[idx] !== null && values[idx + 1] !== null &&
- values[idx + gridW] !== null &&
- values[idx + gridW + 1] !== null && i !== skipColumn) {
- result.elements.push(idxMap[idx]);
- result.elements.push(idxMap[idx + 1]);
- result.elements.push(idxMap[idx + gridW]);
- result.elements.push(idxMap[idx + gridW + 1]);
- result.elements.push(idxMap[idx + gridW]);
- result.elements.push(idxMap[idx + 1]);
- }
- }
- }
- /* Only locate the points that are in use. */
- result.pos = new Array(usedPts * 3);
- result.value = new Array(usedPts);
- result.opacity = new Array(usedPts);
- for (j = i = i3 = 0; j < numPts; j += 1) {
- val = values[j];
- if (val !== null) {
- item = data[j];
- if (usePos) {
- posVal = posFunc(item);
- result.pos[i3] = posVal.x;
- result.pos[i3 + 1] = posVal.y;
- result.pos[i3 + 2] = posVal.z || 0;
- } else {
- if (skipColumn === undefined) {
- result.pos[i3] = x0 + dx * (j % gridW);
- } else {
- result.pos[i3] = calcX[j % gridW];
- }
- result.pos[i3 + 1] = y0 + dy * Math.floor(j / gridW);
- result.pos[i3 + 2] = 0;
- }
- result.opacity[i] = opacityFunc(item);
- if (rangeValues && val >= result.minValue && val <= result.maxValue) {
- for (k = 1; k < rangeValues.length; k += 1) {
- if (val <= rangeValues[k]) {
- result.value[i] = k - 1 + (val - rangeValues[k - 1]) /
- (rangeValues[k] - rangeValues[k - 1]);
- break;
- }
- }
- } else {
- result.value[i] = (val - result.minValue) * result.factor;
- }
- i += 1;
- i3 += 3;
+ * Point searce method for selection api. Returns markers containing the
+ * given point.
+ * @argument {Object} coordinate
+ * @returns {Object}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.pointSearch = function (coordinate) {
+ var found = [], indices = [], data = m_this.data();
+ m_coordinates.forEach(function (coord, i) {
+ var inside = geo.util.pointInPolygon(
+ coordinate,
+ coord.outer,
+ coord.inner
+ );
+ if (inside) {
+ indices.push(i);
+ found.push(data[i]);
}
- }
- return result;
+ });
+ return {
+ index: indices,
+ found: found
+ };
};
////////////////////////////////////////////////////////////////////////////
@@ -20892,20 +23147,15 @@ geo.contourFeature = function (arg) {
var defaultStyle = $.extend(
{},
{
- opacity: 1.0,
- position: function (d) {
- return {x: d.x, y: d.y, z: d.z};
- },
- value: function (d) {
- return m_this.position()(d).z;
- }
+ "fillColor": { r: 0.0, g: 0.5, b: 0.5 },
+ "fillOpacity": 1.0
},
arg.style === undefined ? {} : arg.style
);
m_this.style(defaultStyle);
- if (m_contour) {
+ if (m_position) {
m_this.dataTime().modified();
}
};
@@ -20914,1607 +23164,1493 @@ geo.contourFeature = function (arg) {
return this;
};
-inherit(geo.contourFeature, geo.feature);
-
-/* Example:
-
-layer.createFeature('contour', {
-})
-.data()
-.position(function (d) {
- return { x: , y: , z: };
-})
-.style({
- opacity: function (d) {
- return ;
- },
- value: function (d) { // defaults to position().z
- return ;
- }
-})
-.contour({
- gridWidth: ,
- gridHeight: ,
- x0: ,
- y0: ,
- dx: ,
- dy: ,
- wrapLongitude: ,
- min: ,
- max: ,
- minColor: ,
- minOpacity: ,
- maxColor: ,
- maxOpacity: ,
- stepped: ,
- colorRange: [],
- opacityRange: [],
- rangeValues: []
-})
-
-Notes:
-* The position array is only used for position if not all of x0, y0, dx, and dy
- are specified (not null or undefined). If a value array is not specified,
- the position array could still be used for the value.
-* If the value() of a grid point is null or undefined, that point will not be
- included in the contour display. Since the values are on a grid, if this
- point is in the interior of the grid, this can remove up to four squares.
-* Only one of gridWidth and gridHeight needs to be specified. If both are
- specified and gridWidth * gridHeight < data().length, not all the data will
- be used. If neither are specified, floor(sqrt(data().length)) is used for
- both.
- */
+inherit(geo.polygonFeature, geo.feature);
//////////////////////////////////////////////////////////////////////////////
/**
- * Transform geometric data of a feature from source projection to destination
- * projection.
+ * Create a new instance of class planeFeature
*
- * @namespace
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.transform = {};
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Custom transform for a feature used for OpenStreetMap
+ * @class
+ * @extends geo.polygonFeature
+ * @returns {geo.planeFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.transform.osmTransformFeature = function (destGcs, feature, inplace) {
- /// TODO
- /// Currently we make assumption that incoming feature is in 4326
- /// which may not be true.
-
+geo.planeFeature = function (arg) {
"use strict";
-
- if (!feature) {
- console.log("[warning] Invalid (null) feature");
- return;
- }
-
- if (feature.gcs() === destGcs) {
- return;
+ if (!(this instanceof geo.planeFeature)) {
+ return new geo.planeFeature(arg);
}
+ arg = arg || {};
- if (!(feature instanceof geo.pointFeature ||
- feature instanceof geo.lineFeature)) {
- throw "Supports only point or line feature";
- }
+ // Defaults
+ arg.ul = arg.ul === undefined ? [0.0, 1.0, 0.0] : arg.ul;
+ arg.lr = arg.lr === undefined ? [1.0, 0.0, 0.0] : arg.lr;
+ arg.depth = arg.depth === undefined ? 0.0 : arg.depth;
- var noOfComponents = null,
- pointOffset = 0,
- count = null,
- inPos = null,
- outPos = null,
- srcGcs = feature.gcs(),
- i,
- yCoord;
+ geo.polygonFeature.call(this, arg);
- inplace = !!inplace;
- if (feature instanceof geo.pointFeature ||
- feature instanceof geo.lineFeature) {
+ var m_this = this,
+ m_origin = [arg.ul.x, arg.lr.y, arg.depth],
+ m_upperLeft = [arg.ul.x, arg.ul.y, arg.depth],
+ m_lowerRight = [arg.lr.x, arg.lr.y, arg.depth],
+ m_defaultDepth = arg.depth,
+ m_drawOnAsyncResourceLoad = arg.drawOnAsyncResourceLoad === undefined ?
+ true : false,
+ s_init = this._init;
- /// If source GCS is not in 4326, transform it first into 4326
- /// before we transform it for OSM.
- if (srcGcs !== "EPSG:4326") {
- geo.transform.transformFeature("EPSG:4326", feature, true);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set origin
+ *
+ * @returns {geo.planeFeature}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.origin = function (val) {
+ if (val === undefined) {
+ return m_origin;
+ } else if (val instanceof Array) {
+ if (val.length > 3 || val.length < 2) {
+ throw "Origin point requires point in 2 or 3 dimension";
+ }
+ m_origin = val.slice(0);
+ if (m_origin.length === 2) {
+ m_origin[2] = m_defaultDepth;
+ }
}
+ m_this.dataTime().modified();
+ m_this.modified();
+ return m_this;
+ };
- inPos = feature.positions();
- count = inPos.length;
-
- if (!(inPos instanceof Array)) {
- throw "Supports Array of 2D and 3D points";
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set pt1
+ *
+ * @returns {geo.planeFeature}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.upperLeft = function (val) {
+ if (val === undefined) {
+ return m_upperLeft;
+ } else if (val instanceof Array) {
+ if (val.length > 3 || val.length < 2) {
+ throw "Upper left point requires point in 2 or 3 dimension";
+ }
+ m_upperLeft = val.slice(0);
+ if (m_upperLeft.length === 2) {
+ m_upperLeft[2] = m_defaultDepth;
+ }
}
+ m_this.dataTime().modified();
+ m_this.modified();
+ return m_this;
+ };
- noOfComponents = (count % 2 === 0 ? 2 :
- (count % 3 === 0 ? 3 : null));
- pointOffset = noOfComponents;
-
- if (noOfComponents !== 2 && noOfComponents !== 3) {
- throw "Transform points require points in 2D or 3D";
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set origin
+ *
+ * @returns {geo.planeFeature}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.lowerRight = function (val) {
+ if (val === undefined) {
+ return m_lowerRight;
+ } else if (val instanceof Array) {
+ if (val.length > 3 || val.length < 2) {
+ throw "Lower right point requires point in 2 or 3 dimension";
+ }
+ m_lowerRight = val.slice(0);
+ if (m_lowerRight.length === 2) {
+ m_lowerRight[2] = m_defaultDepth;
+ }
+ m_this.dataTime().modified();
}
+ m_this.dataTime().modified();
+ m_this.modified();
+ return m_this;
+ };
- if (inplace) {
- outPos = inPos;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set if draw should happen as soon as a async resource is loaded
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.drawOnAsyncResourceLoad = function (val) {
+ if (val === undefined) {
+ return m_drawOnAsyncResourceLoad;
} else {
- outPos = inPos.slice(0);
- }
-
- for (i = 0; i < count; i += pointOffset) {
-
- /// Y goes from 0 (top edge is 85.0511 °N) to 2zoom − 1
- /// (bottom edge is 85.0511 °S) in a Mercator projection.
- yCoord = inPos[i + 1];
-
- if (yCoord > 85.0511) {
- yCoord = 85.0511;
- }
- if (yCoord < -85.0511) {
- yCoord = -85.0511;
- }
- outPos[i + 1] = geo.mercator.lat2y(yCoord);
+ m_drawOnAsyncResourceLoad = val;
+ return m_this;
}
+ };
- if (inplace) {
- feature.positions(outPos);
- feature.gcs(destGcs);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ var style = null;
+ s_init.call(m_this, arg);
+ style = m_this.style();
+ if (style.image === undefined) {
+ style.image = null;
}
- return outPos;
- }
+ m_this.style(style);
+ };
- return null;
+ this._init(arg);
+ return this;
};
+inherit(geo.planeFeature, geo.polygonFeature);
+
//////////////////////////////////////////////////////////////////////////////
/**
- * Transform a feature to destination GCS
+ * Create a new instance of class vectorFeature
+ *
+ * @class
+ * @extends geo.feature
+ * @returns {geo.vectorFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.transform.transformFeature = function (destGcs, feature, inplace) {
- "use strict";
-
- if (!feature) {
- throw "Invalid (null) feature";
- }
-
- if (!(feature instanceof geo.pointFeature ||
- feature instanceof geo.lineFeature)) {
- throw "Supports only point or line feature";
- }
-
- if (feature.gcs() === destGcs) {
- return feature.positions();
- }
-
- if (destGcs === "EPSG:3857") {
- return geo.transform.osmTransformFeature(destGcs, feature, inplace);
- }
-
- var noOfComponents = null,
- pointOffset = 0,
- count = null,
- inPos = null,
- outPos = null,
- projPoint = null,
- srcGcs = feature.gcs(),
- i,
- projSrcGcs = new proj4.Proj(srcGcs),
- projDestGcs = new proj4.Proj(destGcs);
-
- inplace = !!inplace;
- if (feature instanceof geo.pointFeature ||
- feature instanceof geo.lineFeature) {
- inPos = feature.positions();
- count = inPos.length;
-
- if (!(inPos instanceof Array)) {
- throw "Supports Array of 2D and 3D points";
- }
+geo.vectorFeature = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.vectorFeature)) {
+ return new geo.vectorFeature(arg);
+ }
+ arg = arg || {};
+ geo.feature.call(this, arg);
- noOfComponents = (count % 2 === 0 ? 2 :
- (count % 3 === 0 ? 3 : null));
- pointOffset = noOfComponents;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ s_init = this._init,
+ s_style = this.style;
- if (noOfComponents !== 2 && noOfComponents !== 3) {
- throw "Transform points require points in 2D or 3D";
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get or set the accessor for the origin of the vector. This is the point
+ * that the vector base resides at. Defaults to (0, 0, 0).
+ * @param {geo.accessor|geo.geoPosition} [accessor] The origin accessor
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.origin = function (val) {
+ if (val === undefined) {
+ return s_style('origin');
+ } else {
+ s_style('origin', val);
+ m_this.dataTime().modified();
+ m_this.modified();
}
+ return m_this;
+ };
- if (inplace) {
- outPos = inPos;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get or set the accessor for the displacement (coordinates) of the vector.
+ * @param {geo.accessor|geo.geoPosition} [accessor] The accessor
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.delta = function (val) {
+ if (val === undefined) {
+ return s_style('delta');
} else {
- outPos = [];
- outPos.length = inPos.length;
+ s_style('delta', val);
+ m_this.dataTime().modified();
+ m_this.modified();
}
+ return m_this;
+ };
- for (i = 0; i < count; i += pointOffset) {
- if (noOfComponents === 2) {
- projPoint = new proj4.Point(inPos[i], inPos[i + 1], 0.0);
- } else {
- projPoint = new proj4.Point(inPos[i], inPos[i + 1], inPos[i + 2]);
- }
-
- proj4.transform(projSrcGcs, projDestGcs, projPoint);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize
+ * @protected
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
- if (noOfComponents === 2) {
- outPos[i] = projPoint.x;
- outPos[i + 1] = projPoint.y;
- } else {
- outPos[i] = projPoint.x;
- outPos[i + 1] = projPoint.y;
- outPos[i + 2] = projPoint.z;
- }
- }
+ var defaultStyle = $.extend(
+ {},
+ {
+ strokeColor: 'black',
+ strokeWidth: 2.0,
+ strokeOpacity: 1.0,
+ // TODO: define styles for the end markers
+ // originStyle: 'none',
+ // endStyle: 'arrow',
+ origin: {x: 0, y: 0, z: 0},
+ delta: function (d) { return d; },
+ scale: null // size scaling factor (null -> renderer decides)
+ },
+ arg.style === undefined ? {} : arg.style
+ );
- if (inplace) {
- feature.positions(outPos);
- feature.gcs(destGcs);
+ if (arg.origin !== undefined) {
+ defaultStyle.origin = arg.origin;
}
- return outPos;
- }
-
- return null;
+ m_this.style(defaultStyle);
+ m_this.dataTime().modified();
+ };
};
+inherit(geo.vectorFeature, geo.feature);
+
//////////////////////////////////////////////////////////////////////////////
/**
- * Transform geometric data of a layer from source projection to destination
- * projection.
+ * Create a new instance of class geomFeature
+ *
+ * @class
+ * @extends geo.feature
+ * @returns {geo.geomFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.transform.transformLayer = function (destGcs, layer, baseLayer) {
+geo.geomFeature = function (arg) {
"use strict";
-
- var features, count, i;
-
- if (!layer) {
- throw "Requires valid layer for tranformation";
- }
-
- if (!baseLayer) {
- throw "Requires baseLayer used by the map";
- }
-
- if (layer === baseLayer) {
- return;
+ if (!(this instanceof geo.geomFeature)) {
+ return new geo.geomFeature(arg);
}
+ arg = arg || {};
+ geo.feature.call(this, arg);
- if (layer instanceof geo.featureLayer) {
- features = layer.features();
- count = features.length;
- i = 0;
+ arg.style = arg.style === undefined ? $.extend({}, {
+ "color": [1.0, 1.0, 1.0],
+ "point_sprites": false,
+ "point_sprites_image": null
+ }, arg.style) : arg.style;
- for (i = 0; i < count; i += 1) {
- if (destGcs === "EPSG:3857" && baseLayer instanceof geo.osmLayer) {
- geo.transform.osmTransformFeature(
- destGcs, features[i], true);
- } else {
- geo.transform.transformFeature(
- destGcs, features[i], true);
- }
- }
+ // Update style
+ this.style(arg.style);
- layer.gcs(destGcs);
- } else {
- throw "Only feature layer transformation is supported";
- }
+ return this;
};
+inherit(geo.geomFeature, geo.feature);
+
//////////////////////////////////////////////////////////////////////////////
/**
- * Transform position coordinates from source projection to destination
- * projection.
+ * Create a new instance of class graphFeature
*
- * @param {string} srcGcs GCS of the coordinates
- * @param {string} destGcs Desired GCS of the transformed coordinates
- * @param {object} coordinates
- * @return {object|object[]} Transformed coordinates
+ * @class
+ * @extends geo.feature
+ * @returns {geo.graphFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.transform.transformCoordinates = function (srcGcs, destGcs, coordinates,
- numberOfComponents) {
+geo.graphFeature = function (arg) {
"use strict";
- var i, count, offset, xCoord, yCoord, zCoord, xAcc,
- yAcc, zAcc, writer, output, projPoint,
- projSrcGcs = new proj4.Proj(srcGcs),
- projDestGcs = new proj4.Proj(destGcs);
+ if (!(this instanceof geo.graphFeature)) {
+ return new geo.graphFeature(arg);
+ }
+ arg = arg || {};
+ geo.feature.call(this, arg);
- /// Default Z accessor
- zAcc = function () {
- return 0.0;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ s_draw = this.draw,
+ s_style = this.style,
+ m_nodes = null,
+ m_points = null,
+ m_children = function (d) { return d.children; },
+ m_links = [],
+ s_init = this._init,
+ s_exit = this._exit;
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+
+ var defaultStyle = $.extend(true, {},
+ {
+ nodes: {
+ radius: 5.0,
+ fill: true,
+ fillColor: { r: 1.0, g: 0.0, b: 0.0 },
+ strokeColor: { r: 0, g: 0, b: 0 }
+ },
+ links: {
+ strokeColor: { r: 0.0, g: 0.0, b: 0.0 }
+ },
+ linkType: "path" /* 'path' || 'line' */
+ },
+ arg.style === undefined ? {} : arg.style
+ );
+
+ m_this.style(defaultStyle);
+ m_this.nodes(function (d) { return d; });
};
- if (destGcs === srcGcs) {
- return coordinates;
- }
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Call child _build methods
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._build = function () {
+ m_this.children().forEach(function (child) {
+ child._build();
+ });
+ };
- /// TODO: Can we check for EPSG code?
- if (!destGcs || !srcGcs) {
- throw "Invalid source or destination GCS";
- }
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Call child _update methods
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._update = function () {
+ m_this.children().forEach(function (child) {
+ child._update();
+ });
+ };
- /// Helper methods
- function handleArrayCoordinates() {
- if (coordinates[0] instanceof Array) {
- if (coordinates[0].length === 2) {
- xAcc = function (index) {
- return coordinates[index][0];
- };
- yAcc = function (index) {
- return coordinates[index][1];
- };
- writer = function (index, x, y) {
- output[index] = [x, y];
- };
- } else if (coordinates[0].length === 3) {
- xAcc = function (index) {
- return coordinates[index][0];
- };
- yAcc = function (index) {
- return coordinates[index][1];
- };
- zAcc = function (index) {
- return coordinates[index][2];
- };
- writer = function (index, x, y, z) {
- output[index] = [x, y, z];
- };
- } else {
- throw "Invalid coordinates. Requires two or three components per array";
- }
- } else {
- if (coordinates.length === 2) {
- offset = 2;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Custom _exit method to remove all sub-features
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._exit = function () {
+ m_this.data([]);
+ m_links.forEach(function (link) {
+ link._exit();
+ m_this.removeChild(link);
+ });
+ m_links = [];
+ m_points._exit();
+ m_this.removeChild(m_points);
+ s_exit();
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set style
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.style = function (arg, arg2) {
+ var out = s_style.call(m_this, arg, arg2);
+ if (out !== m_this) {
+ return out;
+ }
+ // set styles for sub-features
+ m_points.style(arg.nodes);
+ m_links.forEach(function (l) {
+ l.style(arg.links);
+ });
+ return m_this;
+ };
- xAcc = function (index) {
- return coordinates[index * offset];
- };
- yAcc = function (index) {
- return coordinates[index * offset + 1];
- };
- writer = function (index, x, y) {
- output[index] = x;
- output[index + 1] = y;
- };
- } else if (coordinates.length === 3) {
- offset = 3;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set links accessor.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.links = function (arg) {
+ if (arg === undefined) {
+ return m_children;
+ }
- xAcc = function (index) {
- return coordinates[index * offset];
- };
- yAcc = function (index) {
- return coordinates[index * offset + 1];
- };
- zAcc = function (index) {
- return coordinates[index * offset + 2];
- };
- writer = function (index, x, y, z) {
- output[index] = x;
- output[index + 1] = y;
- output[index + 2] = z;
- };
- } else if (numberOfComponents) {
- if (numberOfComponents === 2 || numberOfComponents || 3) {
- offset = numberOfComponents;
+ m_children = geo.util.ensureFunction(arg);
+ return m_this;
+ };
- xAcc = function (index) {
- return coordinates[index];
- };
- yAcc = function (index) {
- return coordinates[index + 1];
- };
- if (numberOfComponents === 2) {
- writer = function (index, x, y) {
- output[index] = x;
- output[index + 1] = y;
- };
- } else {
- zAcc = function (index) {
- return coordinates[index + 2];
- };
- writer = function (index, x, y, z) {
- output[index] = x;
- output[index + 1] = y;
- output[index + 2] = z;
- };
- }
- } else {
- throw "Number of components should be two or three";
- }
- } else {
- throw "Invalid coordinates";
- }
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set nodes
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.nodes = function (val) {
+ if (val === undefined) {
+ return m_nodes;
}
- }
+ m_nodes = val;
+ m_this.modified();
+ return m_this;
+ };
- /// Helper methods
- function handleObjectCoordinates() {
- if (coordinates[0] &&
- "x" in coordinates[0] &&
- "y" in coordinates[0]) {
- xAcc = function (index) {
- return coordinates[index].x;
- };
- yAcc = function (index) {
- return coordinates[index].y;
- };
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get internal node feature
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.nodeFeature = function () {
+ return m_points;
+ };
- if ("z" in coordinates[0]) {
- zAcc = function (index) {
- return coordinates[index].z;
- };
- writer = function (index, x, y, z) {
- output[i] = {x: x, y: y, z: z};
- };
- } else {
- writer = function (index, x, y) {
- output[index] = {x: x, y: y};
- };
- }
- } else if (coordinates &&
- "x" in coordinates && "y" in coordinates) {
- xAcc = function () {
- return coordinates.x;
- };
- yAcc = function () {
- return coordinates.y;
- };
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get internal link features
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.linkFeatures = function () {
+ return m_links;
+ };
- if ("z" in coordinates) {
- zAcc = function () {
- return coordinates.z;
- };
- writer = function (index, x, y, z) {
- output = {x: x, y: y, z: z};
- };
- } else {
- writer = function (index, x, y) {
- output = {x: x, y: y};
- };
- }
- } else {
- throw "Invalid coordinates";
- }
- }
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Build the feature for drawing
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.draw = function () {
- if (coordinates instanceof Array) {
- output = [];
- output.length = coordinates.length;
- count = coordinates.length;
+ var layer = m_this.layer(),
+ data = m_this.data(),
+ nLinks = 0,
+ style;
- if (coordinates[0] instanceof Array ||
- coordinates[0] instanceof Object) {
- offset = 1;
+ // get the feature style object
+ style = m_this.style();
- if (coordinates[0] instanceof Array) {
- handleArrayCoordinates();
- } else if (coordinates[0] instanceof Object) {
- handleObjectCoordinates();
- }
- } else {
- handleArrayCoordinates();
- }
- } else if (coordinates && coordinates instanceof Object) {
- count = 1;
- offset = 1;
- if (coordinates && "x" in coordinates && "y" in coordinates) {
- handleObjectCoordinates();
- } else {
- throw "Coordinates are not valid";
- }
- }
+ // Bind data to the point nodes
+ m_points.data(data);
+ m_points.style(style.nodes);
- if (destGcs === "EPSG:3857" && srcGcs === "EPSG:4326") {
- for (i = 0; i < count; i += offset) {
- /// Y goes from 0 (top edge is 85.0511 °N) to 2zoom − 1
- /// (bottom edge is 85.0511 °S) in a Mercator projection.
- xCoord = xAcc(i);
- yCoord = yAcc(i);
- zCoord = zAcc(i);
+ // get links from node connections
+ data.forEach(function (source) {
+ (source.children || []).forEach(function (target) {
+ var link;
+ nLinks += 1;
+ if (m_links.length < nLinks) {
+ link = geo.createFeature(
+ style.linkType, layer, layer.renderer()
+ ).style(style.links);
+ m_this.addChild(link);
+ m_links.push(link);
+ }
+ m_links[nLinks - 1].data([source, target]);
+ });
+ });
- if (yCoord > 85.0511) {
- yCoord = 85.0511;
- }
- if (yCoord < -85.0511) {
- yCoord = -85.0511;
- }
+ m_links.splice(nLinks, m_links.length - nLinks).forEach(function (l) {
+ l._exit();
+ m_this.removeChild(l);
+ });
- writer(i, xCoord, geo.mercator.lat2y(yCoord), zCoord);
- }
+ s_draw();
+ return m_this;
+ };
- return output;
- } else {
- for (i = 0; i < count; i += offset) {
- projPoint = new proj4.Point(xAcc(i), yAcc(i), zAcc(i));
- proj4.transform(projSrcGcs, projDestGcs, projPoint);
- writer(i, projPoint.x, projPoint.y, projPoint.z);
- return output;
- }
+ m_points = geo.createFeature(
+ "point",
+ this.layer(),
+ this.layer().renderer()
+ );
+ m_this.addChild(m_points);
+
+ if (arg.nodes) {
+ this.nodes(arg.nodes);
}
+
+ this._init(arg);
+ return this;
};
+inherit(geo.graphFeature, geo.feature);
+
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class renderer
+ * Create a new instance of class contourFeature
*
* @class
- * @extends geo.object
- * @returns {geo.renderer}
+ * @extends geo.feature
+ * @returns {geo.contourFeature}
+ *
*/
//////////////////////////////////////////////////////////////////////////////
-geo.renderer = function (arg) {
- "use strict";
-
- if (!(this instanceof geo.renderer)) {
- return new geo.renderer(arg);
+geo.contourFeature = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.contourFeature)) {
+ return new geo.contourFeature(arg);
}
- geo.object.call(this);
-
arg = arg || {};
+ geo.feature.call(this, arg);
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
var m_this = this,
- m_layer = arg.layer === undefined ? null : arg.layer,
- m_canvas = arg.canvas === undefined ? null : arg.canvas,
- m_initialized = false;
+ m_contour = {},
+ s_init = this._init,
+ s_data = this.data;
+
+ if (arg.contour === undefined) {
+ m_contour = function (d) {
+ return d;
+ };
+ } else {
+ m_contour = arg.contour;
+ }
////////////////////////////////////////////////////////////////////////////
/**
- * Get layer of the renderer
- *
- * @returns {*}
+ * Override the parent data method to keep track of changes to the
+ * internal coordinates.
*/
////////////////////////////////////////////////////////////////////////////
- this.layer = function () {
- return m_layer;
+ this.data = function (arg) {
+ var ret = s_data(arg);
+ return ret;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get canvas for the renderer
+ * Get/Set contour accessor
+ *
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this.canvas = function (val) {
- if (val === undefined) {
- return m_canvas;
+ this.contour = function (arg1, arg2) {
+ if (arg1 === undefined) {
+ return m_contour;
+ }
+ if (typeof arg1 === 'string' && arg2 === undefined) {
+ return m_contour[arg1];
+ }
+ if (arg2 === undefined) {
+ var contour = $.extend(
+ {},
+ {
+ gridWidth: function () {
+ if (arg1.gridHeight) {
+ return Math.floor(m_this.data().length / arg1.gridHeight);
+ }
+ return Math.floor(Math.sqrt(m_this.data().length));
+ },
+ gridHeight: function () {
+ if (arg1.gridWidth) {
+ return Math.floor(m_this.data().length / arg1.gridWidth);
+ }
+ return Math.floor(Math.sqrt(m_this.data().length));
+ },
+ minColor: 'black',
+ minOpacity: 0,
+ maxColor: 'black',
+ maxOpacity: 0,
+ /* 9-step based on paraview bwr colortable */
+ colorRange: [
+ {r: 0.07514311, g: 0.468049805, b: 1},
+ {r: 0.468487184, g: 0.588057293, b: 1},
+ {r: 0.656658579, g: 0.707001303, b: 1},
+ {r: 0.821573924, g: 0.837809045, b: 1},
+ {r: 0.943467973, g: 0.943498599, b: 0.943398095},
+ {r: 1, g: 0.788626485, b: 0.750707739},
+ {r: 1, g: 0.6289553, b: 0.568237474},
+ {r: 1, g: 0.472800903, b: 0.404551679},
+ {r: 0.916482116, g: 0.236630659, b: 0.209939162}
+ ]
+ },
+ m_contour,
+ arg1
+ );
+ m_contour = contour;
} else {
- m_canvas = val;
- m_this.modified();
+ m_contour[arg1] = arg2;
}
+ m_this.modified();
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get map that this renderer belongs to
+ * A uniform getter that always returns a function even for constant values.
+ * If undefined input, return all the contour values as an object.
+ *
+ * @param {string|undefined} key
+ * @return {function}
*/
////////////////////////////////////////////////////////////////////////////
- this.map = function () {
- if (m_layer) {
- return m_layer.map();
- } else {
- return null;
+ this.contour.get = function (key) {
+ if (key === undefined) {
+ var all = {}, k;
+ for (k in m_contour) {
+ if (m_contour.hasOwnProperty(k)) {
+ all[k] = m_this.contour.get(k);
+ }
+ }
+ return all;
}
+ return geo.util.ensureFunction(m_contour[key]);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get base layer that belongs to this renderer
+ * Get/Set position accessor
+ *
+ * @returns {geo.pointFeature}
*/
////////////////////////////////////////////////////////////////////////////
- this.baseLayer = function () {
- if (m_this.map()) {
- return m_this.map().baseLayer();
+ this.position = function (val) {
+ if (val === undefined) {
+ return m_this.style('position');
+ } else {
+ m_this.style('position', val);
+ m_this.dataTime().modified();
+ m_this.modified();
}
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set if renderer has been initialized
+ * Create a set of vertices, values at the vertices, and opacities at the
+ * vertices. Create a set of triangles of indices into the vertex array.
+ * Create a color and opacity map corresponding to the values.
+ *
+ * @returns: an object with pos, value, opacity, elements, minValue,
+ * maxValue, minColor, maxColor, colorMap, factor. If there is no
+ * contour data that can be used, only elements is guaranteed to
+ * exist, and it will be a zero-length array.
*/
////////////////////////////////////////////////////////////////////////////
- this.initialized = function (val) {
- if (val === undefined) {
- return m_initialized;
- } else {
- m_initialized = val;
- return m_this;
+ this.createContours = function () {
+ var i, i3, j, idx, k, val, numPts, usedPts = 0, usePos, item,
+ idxMap = {},
+ minval, maxval, range,
+ contour = m_this.contour,
+ data = m_this.data(),
+ posFunc = m_this.position(), posVal,
+ gridW = contour.get('gridWidth')(),
+ gridH = contour.get('gridHeight')(),
+ x0 = contour.get('x0')(),
+ y0 = contour.get('y0')(),
+ dx = contour.get('dx')(),
+ dy = contour.get('dy')(),
+ opacityFunc = m_this.style.get('opacity'),
+ opacityRange = contour.get('opacityRange')(),
+ rangeValues = contour.get('rangeValues')(),
+ valueFunc = m_this.style.get('value'), values = [],
+ stepped = contour.get('stepped')(),
+ wrapLong = contour.get('wrapLongitude')(),
+ calcX, skipColumn, x, origI, /* used for wrapping */
+ gridWorig = gridW, /* can be different when wrapping */
+ result = {
+ minValue: contour.get('min')(),
+ maxValue: contour.get('max')(),
+ stepped: stepped === undefined || stepped ? true : false,
+ wrapLongitude: wrapLong === undefined || wrapLong ? true : false,
+ colorMap: [],
+ elements: []
+ };
+ /* Create the min/max colors and the color array */
+ result.minColor = $.extend({a: contour.get('minOpacity')() || 0},
+ geo.util.convertColor(contour.get('minColor')()));
+ result.maxColor = $.extend({a: contour.get('maxOpacity')() || 0},
+ geo.util.convertColor(contour.get('maxColor')()));
+ contour.get('colorRange')().forEach(function (clr, idx) {
+ result.colorMap.push($.extend(
+ {a: opacityRange && opacityRange[idx] !== undefined ?
+ opacityRange[idx] : 1}, geo.util.convertColor(clr)));
+ });
+ /* Determine which values are usable */
+ if (gridW * gridH > data.length) {
+ gridH = Math.floor(data.length) / gridW;
+ }
+ /* If we are not using the position values (we are using x0, y0, dx, dy),
+ * and wrapLongitude is turned on, and the position spans 180 degrees,
+ * duplicate one or two columns of points at opposite ends of the map. */
+ usePos = (x0 === null || x0 === undefined || y0 === null ||
+ y0 === undefined || !dx || !dy);
+ if (!usePos && result.wrapLongitude && (x0 < -180 || x0 > 180 ||
+ x0 + dx * (gridW - 1) < -180 || x0 + dx * (gridW - 1) > 180) &&
+ dx > -180 && dx < 180) {
+ calcX = [];
+ for (i = 0; i < gridW; i += 1) {
+ x = x0 + i * dx;
+ while (x < -180) { x += 360; }
+ while (x > 180) { x -= 360; }
+ if (i && Math.abs(x - calcX[calcX.length - 1]) > 180) {
+ if (x > calcX[calcX.length - 1]) {
+ calcX.push(x - 360);
+ calcX.push(calcX[calcX.length - 2] + 360);
+ } else {
+ calcX.push(x + 360);
+ calcX.push(calcX[calcX.length - 2] - 360);
+ }
+ skipColumn = i;
+ }
+ calcX.push(x);
+ }
+ gridW += 2;
+ if (Math.abs(Math.abs(gridWorig * dx) - 360) < 0.01) {
+ gridW += 1;
+ x = x0 + gridWorig * dx;
+ while (x < -180) { x += 360; }
+ while (x > 180) { x -= 360; }
+ calcX.push(x);
+ }
+ }
+ /* Calculate the value for point */
+ numPts = gridW * gridH;
+ for (i = 0; i < numPts; i += 1) {
+ if (skipColumn === undefined) {
+ val = parseFloat(valueFunc(data[i]));
+ } else {
+ j = Math.floor(i / gridW);
+ origI = i - j * gridW;
+ origI += (origI > skipColumn ? -2 : 0);
+ if (origI >= gridWorig) {
+ origI -= gridWorig;
+ }
+ origI += j * gridWorig;
+ val = parseFloat(valueFunc(data[origI]));
+ }
+ values[i] = isNaN(val) ? null : val;
+ if (values[i] !== null) {
+ idxMap[i] = usedPts;
+ usedPts += 1;
+ if (minval === undefined) {
+ minval = maxval = values[i];
+ }
+ if (values[i] < minval) {
+ minval = values[i];
+ }
+ if (values[i] > maxval) {
+ maxval = values[i];
+ }
+ }
+ }
+ if (!usedPts) {
+ return result;
+ }
+ if (!$.isNumeric(result.minValue)) {
+ result.minValue = minval;
+ }
+ if (!$.isNumeric(result.maxValue)) {
+ result.maxValue = maxval;
+ }
+ if (!rangeValues || rangeValues.length !== result.colorMap.length + 1) {
+ rangeValues = null;
+ }
+ if (rangeValues) { /* ensure increasing monotonicity */
+ for (k = 1; k < rangeValues.length; k += 1) {
+ if (rangeValues[k] > rangeValues[k + 1]) {
+ rangeValues = null;
+ break;
+ }
+ }
+ }
+ if (rangeValues) {
+ result.minValue = rangeValues[0];
+ result.maxValue = rangeValues[rangeValues.length - 1];
+ }
+ range = result.maxValue - result.minValue;
+ if (!range) {
+ result.colorMap = result.colorMap.slice(0, 1);
+ range = 1;
+ rangeValues = null;
+ }
+ result.rangeValues = rangeValues;
+ result.factor = result.colorMap.length / range;
+ /* Create triangles */
+ for (j = idx = 0; j < gridH - 1; j += 1, idx += 1) {
+ for (i = 0; i < gridW - 1; i += 1, idx += 1) {
+ if (values[idx] !== null && values[idx + 1] !== null &&
+ values[idx + gridW] !== null &&
+ values[idx + gridW + 1] !== null && i !== skipColumn) {
+ result.elements.push(idxMap[idx]);
+ result.elements.push(idxMap[idx + 1]);
+ result.elements.push(idxMap[idx + gridW]);
+ result.elements.push(idxMap[idx + gridW + 1]);
+ result.elements.push(idxMap[idx + gridW]);
+ result.elements.push(idxMap[idx + 1]);
+ }
+ }
}
+ /* Only locate the points that are in use. */
+ result.pos = new Array(usedPts * 3);
+ result.value = new Array(usedPts);
+ result.opacity = new Array(usedPts);
+ for (j = i = i3 = 0; j < numPts; j += 1) {
+ val = values[j];
+ if (val !== null) {
+ item = data[j];
+ if (usePos) {
+ posVal = posFunc(item);
+ result.pos[i3] = posVal.x;
+ result.pos[i3 + 1] = posVal.y;
+ result.pos[i3 + 2] = posVal.z || 0;
+ } else {
+ if (skipColumn === undefined) {
+ result.pos[i3] = x0 + dx * (j % gridW);
+ } else {
+ result.pos[i3] = calcX[j % gridW];
+ }
+ result.pos[i3 + 1] = y0 + dy * Math.floor(j / gridW);
+ result.pos[i3 + 2] = 0;
+ }
+ result.opacity[i] = opacityFunc(item);
+ if (rangeValues && val >= result.minValue && val <= result.maxValue) {
+ for (k = 1; k < rangeValues.length; k += 1) {
+ if (val <= rangeValues[k]) {
+ result.value[i] = k - 1 + (val - rangeValues[k - 1]) /
+ (rangeValues[k] - rangeValues[k - 1]);
+ break;
+ }
+ }
+ } else {
+ result.value[i] = (val - result.minValue) * result.factor;
+ }
+ i += 1;
+ i3 += 3;
+ }
+ }
+ return result;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get render API used by the renderer
- */
- ////////////////////////////////////////////////////////////////////////////
- this.api = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Reset to default
- */
- ////////////////////////////////////////////////////////////////////////////
- this.reset = function () {
- return true;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert array of points from world to GCS coordinate space
- */
- ////////////////////////////////////////////////////////////////////////////
- this.worldToGcs = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert array of points from display to GCS space
- */
- ////////////////////////////////////////////////////////////////////////////
- this.displayToGcs = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert array of points from display to GCS space
- */
- ////////////////////////////////////////////////////////////////////////////
- this.gcsToDisplay = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert array of points from world to display space
- */
- ////////////////////////////////////////////////////////////////////////////
- this.worldToDisplay = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert array of points from display to world space
- */
- ////////////////////////////////////////////////////////////////////////////
- this.displayToWorld = function () {
- throw "Should be implemented by derivied classes";
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get mouse coodinates related to canvas
- *
- * @param {object} event
- * @returns {object}
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this.relMouseCoords = function (event) {
- var totalOffsetX = 0,
- totalOffsetY = 0,
- canvasX = 0,
- canvasY = 0,
- currentElement = m_this.canvas();
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
- do {
- totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
- totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
- currentElement = currentElement.offsetParent;
- } while (currentElement);
+ var defaultStyle = $.extend(
+ {},
+ {
+ opacity: 1.0,
+ position: function (d) {
+ return {x: d.x, y: d.y, z: d.z};
+ },
+ value: function (d) {
+ return m_this.position()(d).z;
+ }
+ },
+ arg.style === undefined ? {} : arg.style
+ );
- canvasX = event.pageX - totalOffsetX;
- canvasY = event.pageY - totalOffsetY;
+ m_this.style(defaultStyle);
- return {
- x: canvasX,
- y: canvasY
- };
+ if (m_contour) {
+ m_this.dataTime().modified();
+ }
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Initialize
- */
- ////////////////////////////////////////////////////////////////////////////
- this._init = function () {
- };
+ this._init(arg);
+ return this;
+};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Handle resize event
- */
- ////////////////////////////////////////////////////////////////////////////
- this._resize = function () {
- };
+inherit(geo.contourFeature, geo.feature);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Render
- */
- ////////////////////////////////////////////////////////////////////////////
- this._render = function () {
- };
+/* Example:
- return this;
-};
+layer.createFeature('contour', {
+})
+.data()
+.position(function (d) {
+ return { x: , y: , z: };
+})
+.style({
+ opacity: function (d) {
+ return ;
+ },
+ value: function (d) { // defaults to position().z
+ return ;
+ }
+})
+.contour({
+ gridWidth: ,
+ gridHeight: ,
+ x0: ,
+ y0: ,
+ dx: ,
+ dy: ,
+ wrapLongitude: ,
+ min: ,
+ max: ,
+ minColor: ,
+ minOpacity: ,
+ maxColor: ,
+ maxOpacity: ,
+ stepped: ,
+ colorRange: [],
+ opacityRange: [],
+ rangeValues: []
+})
-inherit(geo.renderer, geo.object);
+Notes:
+* The position array is only used for position if not all of x0, y0, dx, and dy
+ are specified (not null or undefined). If a value array is not specified,
+ the position array could still be used for the value.
+* If the value() of a grid point is null or undefined, that point will not be
+ included in the contour display. Since the values are on a grid, if this
+ point is in the interior of the grid, this can remove up to four squares.
+* Only one of gridWidth and gridHeight needs to be specified. If both are
+ specified and gridWidth * gridHeight < data().length, not all the data will
+ be used. If neither are specified, floor(sqrt(data().length)) is used for
+ both.
+ */
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of osmLayer
+ * Create a new instance of class renderer
*
* @class
- * @extends geo.featureLayer
- *
- * @param {Object} arg - arg can contain following keys: baseUrl,
- * imageFormat (such as png or jpeg), and displayLast
- * (to decide whether or not render tiles from last zoom level).
+ * @extends geo.object
+ * @returns {geo.renderer}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.osmLayer = function (arg) {
- 'use strict';
+geo.renderer = function (arg) {
+ "use strict";
- if (!(this instanceof geo.osmLayer)) {
- return new geo.osmLayer(arg);
+ if (!(this instanceof geo.renderer)) {
+ return new geo.renderer(arg);
}
+ geo.object.call(this);
- // set a default attribution if no other is provided
arg = arg || {};
- if (arg && arg.attribution === undefined) {
- arg.attribution =
- '© OpenStreetMap contributors';
- }
- geo.featureLayer.call(this, arg);
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Private member variables
- *
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
var m_this = this,
- s_exit = this._exit,
- m_tiles = {},
- m_hiddenBinNumber = -1,
- m_lastVisibleBinNumber = -1,
- m_visibleBinNumber = 1000,
- m_pendingNewTiles = [],
- m_pendingInactiveTiles = [],
- m_numberOfCachedTiles = 0,
- m_tileCacheSize = 100,
- m_baseUrl = 'http://tile.openstreetmap.org/',
- m_mapOpacity = 1.0,
- m_imageFormat = 'png',
- m_updateTimerId = null,
- m_lastVisibleZoom = null,
- m_visibleTilesRange = {},
- s_init = this._init,
- m_pendingNewTilesStat = {},
- s_update = this._update,
- m_updateDefer = null,
- m_zoom = null,
- m_tileUrl,
- m_tileUrlFromTemplate,
- m_crossOrigin = 'anonymous';
-
- if (arg && arg.baseUrl !== undefined) {
- m_baseUrl = arg.baseUrl;
- }
-
- if (m_baseUrl.charAt(m_baseUrl.length - 1) !== '/') {
- m_baseUrl += '/';
- }
-
- if (arg && arg.mapOpacity !== undefined) {
- m_mapOpacity = arg.mapOpacity;
- }
- if (arg && arg.imageFormat !== undefined) {
- m_imageFormat = arg.imageFormat;
- }
-
- if (arg && arg.displayLast !== undefined && arg.displayLast) {
- m_lastVisibleBinNumber = 999;
- }
-
- if (arg && arg.useCredentials !== undefined && arg.useCredentials) {
- m_crossOrigin = 'use-credentials';
- }
+ m_layer = arg.layer === undefined ? null : arg.layer,
+ m_canvas = arg.canvas === undefined ? null : arg.canvas,
+ m_initialized = false;
////////////////////////////////////////////////////////////////////////////
/**
- * Returns a url string containing the requested tile. This default
- * version uses the open street map standard, but the user can
- * change the default behavior.
+ * Get layer of the renderer
*
- * @param {integer} zoom The zoom level
- * @param {integer} x The tile from the xth row
- * @param {integer} y The tile from the yth column
- * @private
+ * @returns {*}
*/
////////////////////////////////////////////////////////////////////////////
- m_tileUrl = function (zoom, x, y) {
- return m_baseUrl + zoom + '/' + x +
- '/' + y + '.' + m_imageFormat;
+ this.layer = function () {
+ return m_layer;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Returns an OSM tile server formatting function from a standard format
- * string: replaces , , and .
- *
- * @param {string} base The tile format string
- * @private
+ * Get canvas for the renderer
*/
////////////////////////////////////////////////////////////////////////////
- m_tileUrlFromTemplate = function (base) {
- return function (zoom, x, y) {
- return base.replace('', zoom)
- .replace('', x)
- .replace('', y);
- };
+ this.canvas = function (val) {
+ if (val === undefined) {
+ return m_canvas;
+ } else {
+ m_canvas = val;
+ m_this.modified();
+ }
};
////////////////////////////////////////////////////////////////////////////
/**
- * Check if a tile is visible in current view
- *
- * @private
+ * Get map that this renderer belongs to
*/
////////////////////////////////////////////////////////////////////////////
- function isTileVisible(tile) {
- if (tile.zoom in m_visibleTilesRange) {
- if (tile.index_x >= m_visibleTilesRange[tile.zoom].startX &&
- tile.index_x <= m_visibleTilesRange[tile.zoom].endX &&
- tile.index_y >= m_visibleTilesRange[tile.zoom].startY &&
- tile.index_y <= m_visibleTilesRange[tile.zoom].endY) {
- return true;
- }
+ this.map = function () {
+ if (m_layer) {
+ return m_layer.map();
+ } else {
+ return null;
}
- return false;
- }
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Draw new tiles and remove the old ones
- *
- * @private
+ * Get base layer that belongs to this renderer
*/
////////////////////////////////////////////////////////////////////////////
- function drawTiles() {
- m_this._removeTiles();
- m_this.draw();
- delete m_pendingNewTilesStat[m_updateTimerId];
- }
+ this.baseLayer = function () {
+ if (m_this.map()) {
+ return m_this.map().baseLayer();
+ }
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set tile cache size
+ * Get/Set if renderer has been initialized
*/
////////////////////////////////////////////////////////////////////////////
- this.tileCacheSize = function (val) {
+ this.initialized = function (val) {
if (val === undefined) {
- return m_tileCacheSize;
+ return m_initialized;
+ } else {
+ m_initialized = val;
+ return m_this;
}
- m_tileCacheSize = val;
- m_this.modified();
};
////////////////////////////////////////////////////////////////////////////
/**
- * Get/Set the tile url formatting function.
+ * Get render API used by the renderer
*/
////////////////////////////////////////////////////////////////////////////
- this.tileUrl = function (val) {
- if (val === undefined) {
- return m_tileUrl;
- } else if (typeof val === 'string') {
- m_tileUrl = m_tileUrlFromTemplate(val);
- } else {
- m_tileUrl = val;
- }
- m_this.modified();
- return m_this;
+ this.api = function () {
+ throw "Should be implemented by derivied classes";
};
////////////////////////////////////////////////////////////////////////////
/**
- * Transform a point or array of points in latitude-longitude-altitude
- * space to local space of the layer
- *
- * @param {*} input
- * Input can be of following types:
- *
- * 3. [x1,y1, x2, y2]
- * 4. [[x,y]]
- * 5. {x:val: y:val, z:val},
- * 6. [{x:val: y:val}]
- *
- * returns {x:lon, y:lat}, [{x:lon, y:lat}], [x1,y1, x2, y2], [[x,y]]
+ * Reset to default
*/
////////////////////////////////////////////////////////////////////////////
- this.toLocal = function (input) {
- var i, output, delta;
-
- /// Now handle different data types
- if (input instanceof Array && input.length > 0) {
- output = [];
- output.length = input.length;
-
- if (input[0] instanceof Array) {
- delta = input % 3 === 0 ? 3 : 2;
-
- if (delta === 2) {
- for (i = 0; i < input.length; i += delta) {
- output[i] = input[i];
- output[i + 1] = geo.mercator.lat2y(input[i + 1]);
- }
- } else {
- for (i = 0; i < input.length; i += delta) {
- output[i] = input[i];
- output[i + 1] = geo.mercator.lat2y(input[i + 1]);
- output[i + 2] = input[i + 2];
- }
- }
- } else if (input[0] instanceof Object &&
- 'x' in input[0] && 'y' in input[0] && 'z' in input[0]) {
- /// Input is array of object
- output[i] = { x: input[i].x, y: geo.mercator.lat2y(input[i].y),
- z: input[i].z };
- } else if (input[0] instanceof Object &&
- 'x' in input[0] && 'y' in input[0] && 'z' in input[0]) {
- /// Input is array of object
- output[i] = { x: input[i].x, y: geo.mercator.lat2y(input[i].y)};
- } else if (input.length >= 2) {
- output = input.slice(0);
- output[1] = geo.mercator.lat2y(input[1]);
- }
- } else {
- output = {};
- output.x = input.x;
- output.y = geo.mercator.lat2y(input.y);
- }
-
- return output;
+ this.reset = function () {
+ return true;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Transform a point or array of points in local space to
- * latitude-longitude space
- *
- * @input Input An object, array, of array of objects/array representing 2D
- * point in space. [x,y], [[x,y]], [{x:val: y:val}], {x:val, y:val}
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this.fromLocal = function (input) {
- var i, output;
-
- if (input instanceof Array && input.length > 0) {
- output = [];
- output.length = input.length;
-
- if (input[0] instanceof Object) {
- for (i = 0; i < input.length; i += 1) {
- output[i] = {};
- output[i].x = input[i].x;
- output[i].y = geo.mercator.y2lat(input[i].y);
- }
- } else if (input[0] instanceof Array) {
- for (i = 0; i < input.length; i += 1) {
- output[i] = input[i];
- output[i][1] = geo.mercator.y2lat(input[i][1]);
- }
- } else {
- for (i = 0; i < input.length; i += 1) {
- output[i] = input[i];
- output[i + 1] = geo.mercator.y2lat(input[i + 1]);
- }
- }
- } else {
- output = {};
- output.x = input.x;
- output.y = geo.mercator.y2lat(input.y);
- }
-
- return output;
+ this._init = function () {
};
////////////////////////////////////////////////////////////////////////////
/**
- * Check if a tile exists in the cache
- *
- * @param {number} zoom The zoom value for the map [1-17]
- * @param {number} x X axis tile index
- * @param {number} y Y axis tile index
+ * Handle resize event
*/
////////////////////////////////////////////////////////////////////////////
- this._hasTile = function (zoom, x, y) {
- if (!m_tiles[zoom]) {
- return false;
- }
- if (!m_tiles[zoom][x]) {
- return false;
- }
- if (!m_tiles[zoom][x][y]) {
- return false;
- }
- return true;
+ this._resize = function () {
};
////////////////////////////////////////////////////////////////////////////
/**
- * Create a new tile
- * @param {number} x X axis tile index
- * @param {number} y Y axis tile index
+ * Render
*/
////////////////////////////////////////////////////////////////////////////
- this._addTile = function (request, zoom, x, y) {
- if (!m_tiles[zoom]) {
- m_tiles[zoom] = {};
- }
- if (!m_tiles[zoom][x]) {
- m_tiles[zoom][x] = {};
- }
- if (m_tiles[zoom][x][y]) {
- return;
- }
+ this._render = function () {
+ };
+
+ return this;
+};
+
+inherit(geo.renderer, geo.object);
- /// Compute corner points
- var noOfTilesX = Math.max(1, Math.pow(2, zoom)),
- noOfTilesY = Math.max(1, Math.pow(2, zoom)),
- /// Convert into mercator
- totalLatDegrees = 360.0,
- lonPerTile = 360.0 / noOfTilesX,
- latPerTile = totalLatDegrees / noOfTilesY,
- llx = -180.0 + x * lonPerTile,
- lly = -totalLatDegrees * 0.5 + y * latPerTile,
- urx = -180.0 + (x + 1) * lonPerTile,
- ury = -totalLatDegrees * 0.5 + (y + 1) * latPerTile,
- tile = new Image();
+(function () {
+ 'use strict';
- tile.LOADING = true;
- tile.LOADED = false;
- tile.REMOVED = false;
- tile.REMOVING = false;
- tile.INVALID = false;
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Create a new instance of osmLayer
+ *
+ * @class
+ * @extends geo.featureLayer
+ *
+ * @param {Object} arg - arg can contain following keys: baseUrl,
+ * imageFormat (such as png or jpeg), and displayLast
+ * (to decide whether or not render tiles from last zoom level).
+ */
+ //////////////////////////////////////////////////////////////////////////////
+ geo.osmLayer = function (arg) {
- tile.crossOrigin = m_crossOrigin;
- tile.zoom = zoom;
- tile.index_x = x;
- tile.index_y = y;
- tile.llx = llx;
- tile.lly = lly;
- tile.urx = urx;
- tile.ury = ury;
- tile.lastused = new Date();
+ if (!(this instanceof geo.osmLayer)) {
+ return new geo.osmLayer(arg);
+ }
+ if (arg.mapOpacity !== undefined && arg.opacity === undefined) {
+ arg.opacity = arg.mapOpacity;
+ }
+ geo.tileLayer.call(this, arg);
- tile.src = m_tileUrl(zoom, x, Math.pow(2, zoom) - 1 - y);
+ /* mapOpacity is just another name for the layer opacity. */
+ this.mapOpacity = this.opacity;
- m_tiles[zoom][x][y] = tile;
- m_pendingNewTiles.push(tile);
- m_numberOfCachedTiles += 1;
- return tile;
+ /**
+ * Returns an instantiated imageTile object with the given indices. This
+ * method always returns a new tile object. Use `_getTileCached`
+ * to use the caching layer.
+ * @param {Object} index The tile index
+ * @param {Number} index.x
+ * @param {Number} index.y
+ * @param {Number} index.level
+ * @param {Object} source The tile index used for constructing the url
+ * @param {Number} source.x
+ * @param {Number} source.y
+ * @param {Number} source.level
+ * @returns {geo.tile}
+ */
+ this._getTile = function (index, source) {
+ var urlParams = source || index;
+ return geo.imageTile({
+ index: index,
+ size: {x: this._options.tileWidth, y: this._options.tileHeight},
+ url: this._options.url(urlParams.x, urlParams.y, urlParams.level || 0,
+ this._options.subdomains)
+ });
+ }.bind(this);
};
- ////////////////////////////////////////////////////////////////////////////
+ // Compute the circumference of the earth / 2 in meters for osm layer image bounds
+ var cEarth = Math.PI * geo.util.radiusEarth;
+
/**
- * Clear tiles that are no longer required
+ * This object contains the default options used to initialize the osmLayer.
*/
- ////////////////////////////////////////////////////////////////////////////
- /* jshint -W089 */
- this._removeTiles = function () {
- var i, x, y, tile, zoom, currZoom = m_zoom,
- lastZoom = m_lastVisibleZoom;
-
- if (!m_tiles) {
- return m_this;
- }
+ geo.osmLayer.defaults = $.extend({}, geo.tileLayer.defaults, {
+ minX: -cEarth,
+ maxX: cEarth,
+ minY: -cEarth,
+ maxY: cEarth,
+ minLevel: 0,
+ maxLevel: 18,
+ tileOverlap: 0,
+ tileWidth: 256,
+ tileHeight: 256,
+ tileOffset : function (level) {
+ var s = Math.pow(2, level - 1) * 256;
+ return {x: s, y: s};
+ },
+ wrapX: true,
+ wrapY: false,
+ url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ attribution: 'Tile data © ' +
+ 'OpenStreetMap contributors'
+ });
- for (zoom in m_tiles) {
- for (x in m_tiles[zoom]) {
- for (y in m_tiles[zoom][x]) {
- tile = m_tiles[zoom][x][y];
- if (tile) {
- tile.REMOVING = true;
- m_pendingInactiveTiles.push(tile);
- }
- }
- }
- }
+ inherit(geo.osmLayer, geo.tileLayer);
- /// First remove the tiles if we have cached more than max cached limit
- m_pendingInactiveTiles.sort(function (a, b) {
- return a.lastused - b.lastused;
- });
+ geo.registerLayer('osm', geo.osmLayer);
+})();
- i = 0;
+geo.domRenderer = function (arg) {
+ 'use strict';
- /// Get rid of tiles if we have reached our threshold. However,
- /// If the tile is required for current zoom, then do nothing.
- /// Also do not delete the tile if it is from the previous zoom
- while (m_numberOfCachedTiles > m_tileCacheSize &&
- i < m_pendingInactiveTiles.length) {
- tile = m_pendingInactiveTiles[i];
+ if (!(this instanceof geo.domRenderer)) {
+ return new geo.domRenderer(arg);
+ }
+ geo.renderer.call(this, arg);
- if (isTileVisible(tile)) {
- i += 1;
- } else {
- m_this.deleteFeature(tile.feature);
- delete m_tiles[tile.zoom][tile.index_x][tile.index_y];
- m_pendingInactiveTiles.splice(i, 1);
- m_numberOfCachedTiles -= 1;
- }
- }
+ arg = arg || {};
- for (i = 0; i < m_pendingInactiveTiles.length; i += 1) {
- tile = m_pendingInactiveTiles[i];
- tile.REMOVING = false;
- tile.REMOVED = false;
- if (tile.zoom !== currZoom && tile.zoom === lastZoom) {
- tile.feature.bin(m_lastVisibleBinNumber);
- } else if (tile.zoom !== currZoom) {
- tile.feature.bin(m_hiddenBinNumber);
- } else {
- tile.lastused = new Date();
- tile.feature.bin(m_visibleBinNumber);
- }
- tile.feature._update();
- }
- m_pendingInactiveTiles = [];
+ var m_this = this;
- return m_this;
+ this.api = function () {
+ return 'dom';
};
- /* jshint +W089 */
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Create / delete tiles as necessary
- */
- ////////////////////////////////////////////////////////////////////////////
- this._addTiles = function (request) {
- var feature, ren = m_this.renderer(),
- /// First get corner points
- /// In display coordinates the origin is on top left corner (0, 0)
- llx = 0.0, lly = m_this.height(), urx = m_this.width(), ury = 0.0,
- temp = null, tile = null, tile1x = null, tile1y = null, tile2x = null,
- tile2y = null, invJ = null, i = 0, j = 0, lastStartX, lastStartY,
- lastEndX, lastEndY, currStartX, currStartY, currEndX, currEndY,
- worldPt1 = ren.displayToWorld([llx, lly]),
- worldPt2 = ren.displayToWorld([urx, ury]),
- worldDeltaY = null, displayDeltaY = null,
- worldDelta = null, displayDelta = null,
- noOfTilesRequired = null, worldDeltaPerTile = null,
- minDistWorldDeltaPerTile = null, distWorldDeltaPerTile;
-
- worldPt1[0] = Math.max(worldPt1[0], -180.0);
- worldPt1[0] = Math.min(worldPt1[0], 180.0);
- worldPt1[1] = Math.max(worldPt1[1], -180.0);
- worldPt1[1] = Math.min(worldPt1[1], 180.0);
-
- worldPt2[0] = Math.max(worldPt2[0], -180.0);
- worldPt2[0] = Math.min(worldPt2[0], 180.0);
- worldPt2[1] = Math.max(worldPt2[1], -180.0);
- worldPt2[1] = Math.min(worldPt2[1], 180.0);
-
- /// Compute tile zoom
- worldDelta = Math.abs(worldPt2[0] - worldPt1[0]);
- worldDeltaY = Math.abs(worldPt2[1] - worldPt1[1]);
-
- displayDelta = urx - llx;
- displayDeltaY = lly - ury;
-
- /// Reuse variables
- if (displayDeltaY > displayDelta) {
- displayDelta = displayDeltaY;
- worldDelta = worldDeltaY;
- }
-
- noOfTilesRequired = Math.round(displayDelta / 256.0);
- worldDeltaPerTile = worldDelta / noOfTilesRequired;
-
- /// Minimize per pixel distortion
- minDistWorldDeltaPerTile = Number.POSITIVE_INFINITY;
- for (i = 20; i >= 2; i = i - 1) {
- distWorldDeltaPerTile = Math.abs(360.0 / Math.pow(2, i) - worldDeltaPerTile);
- if (distWorldDeltaPerTile < minDistWorldDeltaPerTile) {
- minDistWorldDeltaPerTile = distWorldDeltaPerTile;
- m_zoom = i;
- }
- }
-
- /// Compute tilex and tiley
- tile1x = geo.mercator.long2tilex(worldPt1[0], m_zoom);
- tile1y = geo.mercator.lat2tiley(worldPt1[1], m_zoom);
-
- tile2x = geo.mercator.long2tilex(worldPt2[0], m_zoom);
- tile2y = geo.mercator.lat2tiley(worldPt2[1], m_zoom);
-
- /// Clamp tilex and tiley
- tile1x = Math.max(tile1x, 0);
- tile1x = Math.min(Math.pow(2, m_zoom) - 1, tile1x);
- tile1y = Math.max(tile1y, 0);
- tile1y = Math.min(Math.pow(2, m_zoom) - 1, tile1y);
-
- tile2x = Math.max(tile2x, 0);
- tile2x = Math.min(Math.pow(2, m_zoom) - 1, tile2x);
- tile2y = Math.max(tile2y, 0);
- tile2y = Math.min(Math.pow(2, m_zoom) - 1, tile2y);
-
- /// Check and update variables appropriately if view
- /// direction is flipped. This should not happen but
- /// just in case.
- if (tile1x > tile2x) {
- temp = tile1x;
- tile1x = tile2x;
- tile2x = temp;
- }
- if (tile2y > tile1y) {
- temp = tile1y;
- tile1y = tile2y;
- tile2y = temp;
- }
-
- /// Compute current tile indices
- currStartX = tile1x;
- currEndX = tile2x;
- currStartY = (Math.pow(2, m_zoom) - 1 - tile1y);
- currEndY = (Math.pow(2, m_zoom) - 1 - tile2y);
- if (currEndY < currStartY) {
- temp = currStartY;
- currStartY = currEndY;
- currEndY = temp;
- }
-
- /// Compute last tile indices
- lastStartX = geo.mercator.long2tilex(worldPt1[0],
- m_lastVisibleZoom);
- lastStartY = geo.mercator.lat2tiley(worldPt1[1],
- m_lastVisibleZoom);
- lastEndX = geo.mercator.long2tilex(worldPt2[0],
- m_lastVisibleZoom);
- lastEndY = geo.mercator.lat2tiley(worldPt2[1],
- m_lastVisibleZoom);
- lastStartY = Math.pow(2, m_lastVisibleZoom) - 1 - lastStartY;
- lastEndY = Math.pow(2, m_lastVisibleZoom) - 1 - lastEndY;
-
- if (lastEndY < lastStartY) {
- temp = lastStartY;
- lastStartY = lastEndY;
- lastEndY = temp;
- }
-
- m_visibleTilesRange = {};
- m_visibleTilesRange[m_zoom] = { startX: currStartX, endX: currEndX,
- startY: currStartY, endY: currEndY };
-
- m_visibleTilesRange[m_lastVisibleZoom] =
- { startX: lastStartX, endX: lastEndX,
- startY: lastStartY, endY: lastEndY };
- m_pendingNewTilesStat[m_updateTimerId] = { total:
- ((tile2x - tile1x + 1) * (tile1y - tile2y + 1)), count: 0 };
-
- for (i = tile1x; i <= tile2x; i += 1) {
- for (j = tile2y; j <= tile1y; j += 1) {
- invJ = (Math.pow(2, m_zoom) - 1 - j);
- if (!m_this._hasTile(m_zoom, i, invJ)) {
- tile = m_this._addTile(request, m_zoom, i, invJ);
- } else {
- tile = m_tiles[m_zoom][i][invJ];
- tile.feature.bin(m_visibleBinNumber);
- if (tile.LOADED && m_updateTimerId in m_pendingNewTilesStat) {
- m_pendingNewTilesStat[m_updateTimerId].count += 1;
- }
- tile.feature._update();
- }
- tile.lastused = new Date();
- tile.updateTimerId = m_updateTimerId;
- }
+ this._init = function () {
+ var layer = m_this.layer().node();
+
+ if (!m_this.canvas() && layer && layer.length) {
+ // The renderer and the UI Layer share the same canvas
+ // at least for now. This renderer is essentially a noop renderer
+ // designed for backwards compatibility
+ m_this.canvas(layer[0]);
}
+ };
- // define a function here to set tile properties after it is loaded
- function tileOnLoad(tile) {
- var defer = $.Deferred();
- m_this.addDeferred(defer);
+ this._init(arg);
+ return this;
+};
- return function () {
- if (tile.INVALID) {
- return;
- }
- tile.LOADING = false;
- tile.LOADED = true;
- if ((tile.REMOVING || tile.REMOVED) &&
- tile.feature &&
- tile.zoom !== m_zoom) {
- tile.feature.bin(m_hiddenBinNumber);
- tile.REMOVING = false;
- tile.REMOVED = true;
- } else {
- tile.REMOVED = false;
- tile.lastused = new Date();
- tile.feature.bin(m_visibleBinNumber);
- }
+inherit(geo.domRenderer, geo.renderer);
- if (tile.updateTimerId === m_updateTimerId &&
- m_updateTimerId in m_pendingNewTilesStat) {
- tile.feature.bin(m_visibleBinNumber);
- m_pendingNewTilesStat[m_updateTimerId].count += 1;
- } else {
- tile.REMOVED = true;
- tile.feature.bin(m_hiddenBinNumber);
- }
- tile.feature._update();
+geo.registerRenderer('dom', geo.domRenderer);
- if (m_updateTimerId in m_pendingNewTilesStat &&
- m_pendingNewTilesStat[m_updateTimerId].count >=
- m_pendingNewTilesStat[m_updateTimerId].total) {
- drawTiles();
- }
- defer.resolve();
- };
- }
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of class choroplethFeature
+ *
+ * @class
+ * @extends geo.feature
+ * @returns {geo.choroplethFeature}
+ *
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.choroplethFeature = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.choroplethFeature)) {
+ return new geo.choroplethFeature(arg);
+ }
+ arg = arg || {};
+ geo.feature.call(this, arg);
- /// And now finally add them
- for (i = 0; i < m_pendingNewTiles.length; i += 1) {
- tile = m_pendingNewTiles[i];
- feature = m_this.createFeature(
- 'plane', {drawOnAsyncResourceLoad: false, onload: tileOnLoad(tile)})
- .origin([tile.llx, tile.lly])
- .upperLeft([tile.llx, tile.ury])
- .lowerRight([tile.urx, tile.lly])
- .gcs('EPSG:3857')
- .style({image: tile, opacity: m_mapOpacity});
- tile.feature = feature;
- tile.feature._update();
- }
- m_pendingNewTiles = [];
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ s_init = this._init,
+ m_choropleth = $
+ .extend({},
+ {
+ /* 9-step based on paraview bwr colortable */
+ colorRange: [
+ {r: 0.07514311, g: 0.468049805, b: 1},
+ {r: 0.468487184, g: 0.588057293, b: 1},
+ {r: 0.656658579, g: 0.707001303, b: 1},
+ {r: 0.821573924, g: 0.837809045, b: 1},
+ {r: 0.943467973, g: 0.943498599, b: 0.943398095},
+ {r: 1, g: 0.788626485, b: 0.750707739},
+ {r: 1, g: 0.6289553, b: 0.568237474},
+ {r: 1, g: 0.472800903, b: 0.404551679},
+ {r: 0.916482116, g: 0.236630659, b: 0.209939162}
+ ],
+ scale: d3.scale.quantize(),
+ accessors: {
+ //accessor for ID on geodata feature
+ geoId: function (geoFeature) {
+ return geoFeature.properties.GEO_ID;
+ },
+ //accessor for ID on scalar element
+ scalarId: function (scalarElement) {
+ return scalarElement.id;
+ },
+ //accessor for value on scalar element
+ scalarValue: function (scalarElement) {
+ return scalarElement.value;
+ }
+ }
+ },
+ arg.choropleth);
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set choropleth scalar data
+ *
+ * @returns {geo.feature.choropleth}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.scalar = function (data, aggregator) {
+ var scalarId, scalarValue;
- if (m_updateTimerId in m_pendingNewTilesStat &&
- m_pendingNewTilesStat[m_updateTimerId].count >=
- m_pendingNewTilesStat[m_updateTimerId].total) {
- drawTiles();
+ if (data === undefined) {
+ return m_this.choropleth.get('scalar')();
+ } else {
+ scalarId = m_this.choropleth.get('accessors')().scalarId;
+ scalarValue = m_this.choropleth.get('accessors')().scalarValue;
+ m_choropleth.scalar = data;
+ m_choropleth.scalarAggregator = aggregator || d3.mean;
+ // we make internal dictionary from array for faster lookup
+ // when matching geojson features to scalar values,
+ // note that we also allow for multiple scalar elements
+ // for the same geo feature
+ m_choropleth.scalar._dictionary = data
+ .reduce(function (accumeDictionary, scalarElement) {
+ var id, value;
+
+ id = scalarId(scalarElement);
+ value = scalarValue(scalarElement);
+
+ accumeDictionary[id] =
+ accumeDictionary[id] ?
+ accumeDictionary[id].push(value) : [value];
+
+ return accumeDictionary;
+ }, {});
+ m_this.dataTime().modified();
}
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update OSM tiles as needed
+ * Get/Set choropleth accessor
+ *
+ * @returns {geo.feature.choropleth}
*/
////////////////////////////////////////////////////////////////////////////
- function updateOSMTiles(request) {
- if (request === undefined) {
- request = {};
- }
+ this.choropleth = function (arg1, arg2) {
+ var choropleth;
- if (!m_zoom) {
- m_zoom = m_this.map().zoom();
+ if (arg1 === undefined) {
+ return m_choropleth;
}
-
- if (!m_lastVisibleZoom) {
- m_lastVisibleZoom = m_zoom;
+ if (typeof arg1 === 'string' && arg2 === undefined) {
+ return m_choropleth[arg1];
}
-
- /// Add tiles that are currently visible
- m_this._addTiles(request);
-
- /// Update the zoom
- if (m_lastVisibleZoom !== m_zoom) {
- m_lastVisibleZoom = m_zoom;
+ if (arg2 === undefined) {
+ choropleth = $.extend(
+ {},
+ m_choropleth,
+ arg1
+ );
+ m_choropleth = choropleth;
+ } else {
+ m_choropleth[arg1] = arg2; //if you pass in accessor for prop
}
-
- m_this.updateTime().modified();
- }
+ m_this.modified();
+ return m_this;
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Create / delete tiles as necessary
+ * A uniform getter that always returns a function even for constant values.
+ * If undefined input, return all the choropleth values as an object.
+ *
+ * @param {string|undefined} key
+ * @return {function}
*/
////////////////////////////////////////////////////////////////////////////
- this._updateTiles = function (request) {
- var defer = $.Deferred();
- m_this.addDeferred(defer);
-
- if (m_updateTimerId !== null) {
- clearTimeout(m_updateTimerId);
- m_updateDefer.resolve();
- m_updateDefer = defer;
- if (m_updateTimerId in m_pendingNewTilesStat) {
- delete m_pendingNewTilesStat[m_updateTimerId];
+ this.choropleth.get = function (key) {
+ var all = {}, k;
+ if (key === undefined) {
+ for (k in m_choropleth) {
+ if (m_choropleth.hasOwnProperty(k)) {
+ all[k] = m_this.choropleth.get(k);
+ }
}
- /// Set timeout for 60 ms. 60 ms seems to playing well
- /// with the events. Also, 60ms corresponds to 15 FPS.
- m_updateTimerId = setTimeout(function () {
- updateOSMTiles(request);
- m_updateDefer.resolve();
- }, 100);
- } else {
- m_updateDefer = defer;
- m_updateTimerId = setTimeout(function () {
- updateOSMTiles(request);
- m_updateDefer.resolve();
- }, 0);
+ return all;
}
- return m_this;
+ return geo.util.ensureFunction(m_choropleth[key]);
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Initialize
- */
- ////////////////////////////////////////////////////////////////////////////
- this._init = function () {
- s_init.call(m_this);
- m_this.gcs('EPSG:3857');
- m_this.map().zoomRange({
- min: 0,
- max: 18
- });
- return m_this;
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * A method that adds a polygon feature to the current layer.
+ *
+ * @param {array} coordinateArray
+ * @param {geo.color} fillColor
+ * @return {geo.feature}
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._addPolygonFeature = function (feature, fillColor) {
+ var newFeature = m_this.layer()
+ .createFeature('polygon', {});
+
+ if (feature.geometry.type === 'Polygon') {
+ newFeature.data([{
+ type: 'Polygon',
+ coordinates: feature.geometry.coordinates
+ }]);
+ } else if (feature.geometry.type === 'MultiPolygon') {
+ newFeature.data(feature.geometry.coordinates.map(function (coordinateMap) {
+ return {
+ type: 'Polygon',
+ coordinates: coordinateMap
+ };
+ }));
+ }
+
+ newFeature
+ .polygon(function (d) {
+ return {
+ 'outer': d.coordinates[0],
+ 'inner': d.coordinates[1] // undefined but ok
+ };
+ })
+ .position(function (d) {
+ return {
+ x: d[0],
+ y: d[1]
+ };
+ })
+ .style({
+ 'fillColor': fillColor
+ });
+
+ return newFeature;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update layer
+ * A method that adds polygons from a given feature to the current layer.
+ *
+ * @param {} geoJsonFeature
+ * @param geo.color
+ * @return [{geo.feature}]
*/
////////////////////////////////////////////////////////////////////////////
- this._update = function (request) {
- /// Update tiles (create new / delete old etc...)
- m_this._updateTiles(request);
-
- /// Now call base class update
- s_update.call(m_this, request);
+ this._featureToPolygons = function (feature, fillValue) {
+ return m_this
+ ._addPolygonFeature(feature, fillValue);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update baseUrl for map tiles. Map all tiles as needing to be refreshed.
- * If no argument is given the tiles will be forced refreshed.
+ * A method that sets a choropleth scale's domain and range.
*
- * @param baseUrl: the new baseUrl for the map.
+ * @param {undefined | function({})} valueAccessor
+ * @return {geo.feature.choropleth}
*/
////////////////////////////////////////////////////////////////////////////
- /* jshint -W089 */
- this.updateBaseUrl = function (baseUrl) {
- if (baseUrl && baseUrl.charAt(m_baseUrl.length - 1) !== '/') {
- baseUrl += '/';
- }
- if (baseUrl !== m_baseUrl) {
+ this._generateScale = function (valueAccessor) {
+ var extent =
+ d3.extent(m_this.scalar(), valueAccessor || undefined);
- if (baseUrl !== undefined) {
- m_baseUrl = baseUrl;
- }
-
- var tile, x, y, zoom;
- for (zoom in m_tiles) {
- for (x in m_tiles[zoom]) {
- for (y in m_tiles[zoom][x]) {
- tile = m_tiles[zoom][x][y];
- tile.INVALID = true;
- m_this.deleteFeature(tile.feature);
- }
- }
- }
- m_tiles = {};
- m_pendingNewTiles = [];
- m_pendingInactiveTiles = [];
- m_numberOfCachedTiles = 0;
- m_visibleTilesRange = {};
- m_pendingNewTilesStat = {};
+ m_this.choropleth()
+ .scale
+ .domain(extent)
+ .range(m_this.choropleth().colorRange);
- if (m_updateTimerId !== null) {
- clearTimeout(m_updateTimerId);
- m_updateTimerId = null;
- }
- this._update();
- }
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
- /**
- * Get/Set map opacity
- *
- * @returns {geo.osmLayer}
+ /**sr
+ * Generate scale for choropleth.data(), make polygons from features.
+ * @returns: [ [geo.feature.polygon, ...] , ... ]
*/
////////////////////////////////////////////////////////////////////////////
- this.mapOpacity = function (val) {
- if (val === undefined) {
- return m_mapOpacity;
- } else if (val !== m_mapOpacity) {
- m_mapOpacity = val;
- var zoom, x, y, tile;
- for (zoom in m_tiles) {
- for (x in m_tiles[zoom]) {
- for (y in m_tiles[zoom][x]) {
- tile = m_tiles[zoom][x][y];
- tile.feature.style().opacity = val;
- tile.feature._update();
- }
- }
- }
- m_this.modified();
- }
- return m_this;
+ this.createChoropleth = function () {
+ var choropleth = m_this.choropleth,
+ data = m_this.data(),
+ scalars = m_this.scalar(),
+ valueFunc = choropleth.get('accessors')().scalarValue,
+ getFeatureId = choropleth.get('accessors')().geoId;
+
+ m_this._generateScale(valueFunc);
+
+ return data
+ .map(function (feature) {
+ var id = getFeatureId(feature);
+ var valueArray = scalars._dictionary[id];
+ var accumulatedScalarValue = choropleth().scalarAggregator(valueArray);
+ // take average of this array of values
+ // which allows for non-bijective correspondance
+ // between geo data and scalar data
+ var fillColor =
+ m_this
+ .choropleth()
+ .scale(accumulatedScalarValue);
+
+ return m_this
+ ._featureToPolygons(feature, fillColor);
+ });
};
////////////////////////////////////////////////////////////////////////////
/**
- * Exit
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this._exit = function () {
- m_tiles = {};
- m_pendingNewTiles = [];
- m_pendingInactiveTiles = [];
- m_numberOfCachedTiles = 0;
- m_visibleTilesRange = {};
- m_pendingNewTilesStat = {};
- s_exit();
- };
-
- if (arg && arg.tileUrl !== undefined) {
- this.tileUrl(arg.tileUrl);
- }
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+ if (m_choropleth) {
+ m_this.dataTime().modified();
+ }
+ };
+ this._init(arg);
return this;
};
-inherit(geo.osmLayer, geo.featureLayer);
+inherit(geo.choroplethFeature, geo.feature);
-geo.registerLayer('osm', geo.osmLayer);
+/* Example:
+ */
/**
* @namespace
*/
geo.gl = {};
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class vglRenderer
- *
- * @class
- * @extends geo.renderer
- * @param canvas
- * @returns {geo.gl.renderer}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.gl.renderer = function (arg) {
- 'use strict';
-
- if (!(this instanceof geo.gl.renderer)) {
- return new geo.gl.renderer(arg);
- }
- geo.renderer.call(this, arg);
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get context specific renderer
- */
- ////////////////////////////////////////////////////////////////////////////
- this.contextRenderer = function () {
- throw 'Should be implemented by derived classes';
- };
-
- return this;
-};
-
-inherit(geo.gl.renderer, geo.renderer);
-
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of lineFeature
@@ -22771,7 +24907,7 @@ geo.gl.lineFeature = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
this.verticesPerFeature = function () {
- return this.featureVertices().length;
+ return m_this.featureVertices().length;
};
////////////////////////////////////////////////////////////////////////////
@@ -23521,7 +25657,7 @@ geo.gl.geomFeature = function (arg) {
m_mapper.setGeometryData(m_geom);
}
- this.setMapper(m_mapper);
+ m_this.setMapper(m_mapper);
if (style.point_sprites !== undefined && style.point_sprites &&
style.point_sprites_image !== undefined &&
@@ -23642,8 +25778,11 @@ geo.gl.planeFeature = function (arg) {
image = null,
texture = null;
+ //DWM:: we are expecting the features to be in the same coordinate system
+ //DWM:: as the camera -- should it be otherwise?
/// TODO If for some reason base layer changes its gcs at run time
/// then we need to trigger an event to rebuild every feature
+ /*
or = geo.transform.transformCoordinates(m_this.gcs(),
m_this.layer().map().gcs(),
or);
@@ -23653,6 +25792,7 @@ geo.gl.planeFeature = function (arg) {
lr = geo.transform.transformCoordinates(m_this.gcs(),
m_this.layer().map().gcs(),
lr);
+ */
m_this.buildTime().modified();
@@ -24406,7 +26546,7 @@ geo.registerFeature('vgl', 'contour', geo.gl.contourFeature);
* Create a new instance of class vglRenderer
*
* @class
- * @extends geo.gl.renderer
+ * @extends geo.renderer
* @param canvas
* @returns {geo.gl.vglRenderer}
*/
@@ -24418,14 +26558,14 @@ geo.gl.vglRenderer = function (arg) {
return new geo.gl.vglRenderer(arg);
}
arg = arg || {};
- geo.gl.renderer.call(this, arg);
+ geo.renderer.call(this, arg);
var m_this = this,
m_contextRenderer = null,
m_viewer = null,
m_width = 0,
m_height = 0,
- m_initialized = false,
+ m_renderAnimFrameRef = null,
s_init = this._init;
/// TODO: Move this API to the base class
@@ -24447,160 +26587,6 @@ geo.gl.vglRenderer = function (arg) {
return m_height;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert input data in display space to world space
- *
- * @param {object} input {x:val, y:val}, [{x:val, y:val}],
- * [{x:val, y:val}], [x1,y1], [[x,y]]
- *
- * @returns {object} {x:val, y:val, z:val, w:val}, [{x:val, y:val, z:val, w:val}],
- [[x, y, z, w]], [x1,y1,z1,w]
- */
- ////////////////////////////////////////////////////////////////////////////
- this.displayToWorld = function (input) {
- var i,
- delta,
- ren = m_this.contextRenderer(),
- cam = ren.camera(),
- fdp = ren.focusDisplayPoint(),
- output,
- temp,
- point;
-
- /// Handle if the input is an array [...]
- if (input instanceof Array && input.length > 0) {
- output = [];
- /// Input is array of object {x:val, y:val}
- if (input[0] instanceof Object) {
- delta = 1;
- for (i = 0; i < input.length; i += delta) {
- point = input[i];
- temp = ren.displayToWorld(vec4.fromValues(
- point.x, point.y, fdp[2], 1.0),
- cam.viewMatrix(), cam.projectionMatrix(),
- m_width, m_height);
- output.push({x: temp[0], y: temp[1], z: temp[2], w: temp[3]});
- }
- /// Input is array of 2d array [[x,y], [x,y]]
- } else if (input[0] instanceof Array) {
- delta = 1;
- for (i = 0; i < input.length; i += delta) {
- point = input[i];
- temp = ren.displayToWorld(vec4.fromValues(
- point[0], point[1], fdp[2], 1.0),
- cam.viewMatrix(), cam.projectionMatrix(),
- m_width, m_height);
- output.push(temp);
- }
- /// Input is flat array [x1,y1,x2,y2]
- } else {
- delta = input.length % 3 === 0 ? 3 : 2;
- for (i = 0; i < input.length; i += delta) {
- temp = ren.displayToWorld(vec4.fromValues(
- input[i],
- input[i + 1],
- fdp[2],
- 1.0), cam.viewMatrix(), cam.projectionMatrix(),
- m_width, m_height);
- output.push(temp[0]);
- output.push(temp[1]);
- output.push(temp[2]);
- output.push(temp[3]);
- }
- }
- /// Input is object {x:val, y:val}
- } else if (input instanceof Object) {
- output = {};
- temp = ren.displayToWorld(vec4.fromValues(
- input.x, input.y, fdp[2], 1.0),
- cam.viewMatrix(), cam.projectionMatrix(),
- m_width, m_height);
- output = {x: temp[0], y: temp[1], z: temp[2], w: temp[3]};
- } else {
- throw 'Display to world conversion requires array of 2D/3D points';
- }
- return output;
- };
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert input data in world space to display space
- *
- * @param {object} input {x:val, y:val} or {x:val, y:val, z:val} or [{x:val, y:val}]
- * [{x:val, y:val, z:val}] or [[x,y]] or [[x,y,z]] or [x1,y1,z1, x2, y2, z2]
- *
- * @returns {object} {x:val, y:val} or [{x:val, y:val}] or [[x,y]] or
- * [x1,y1, x2, y2]
- */
- ////////////////////////////////////////////////////////////////////////////
- this.worldToDisplay = function (input) {
- var i, temp, delta,
- ren = m_this.contextRenderer(), cam = ren.camera(),
- fp = cam.focalPoint(), output = [];
-
- /// Input is an array
- if (input instanceof Array && input.length > 0) {
- output = [];
-
- /// Input is an array of objects
- if (input[0] instanceof Object) {
- delta = 1;
- for (i = 0; i < input.length; i += delta) {
- temp = ren.worldToDisplay(vec4.fromValues(
- input[i].x, input[i].y, fp[2], 1.0), cam.viewMatrix(),
- cam.projectionMatrix(),
- m_width, m_height);
- output[i] = { x: temp[0], y: temp[1], z: temp[2] };
- }
- } else if (input[0] instanceof Array) {
- /// Input is an array of array
- delta = 1;
- for (i = 0; i < input.length; i += delta) {
- temp = ren.worldToDisplay(
- vec4.fromValues(input[i][0], input[i][1], fp[2], 1.0),
- cam.viewMatrix(), cam.projectionMatrix(), m_width, m_height);
- output[i].push(temp);
- }
- } else {
- /// Input is a flat array of 2 or 3 dimension
- delta = input.length % 3 === 0 ? 3 : 2;
- if (delta === 2) {
- for (i = 0; i < input.length; i += delta) {
- temp = ren.worldToDisplay(vec4.fromValues(
- input[i], input[i + 1], fp[2], 1.0), cam.viewMatrix(),
- cam.projectionMatrix(),
- m_width, m_height);
- output.push(temp[0]);
- output.push(temp[1]);
- output.push(temp[2]);
- }
- } else {
- for (i = 0; i < input.length; i += delta) {
- temp = ren.worldToDisplay(vec4.fromValues(
- input[i], input[i + 1], input[i + 2], 1.0), cam.viewMatrix(),
- cam.projectionMatrix(),
- m_width, m_height);
- output.push(temp[0]);
- output.push(temp[1]);
- output.push(temp[2]);
- }
- }
- }
- } else if (input instanceof Object) {
- temp = ren.worldToDisplay(vec4.fromValues(
- input.x, input.y, fp[2], 1.0), cam.viewMatrix(),
- cam.projectionMatrix(),
- m_width, m_height);
-
- output = {x: temp[0], y: temp[1], z: temp[2]};
- } else {
- throw 'World to display conversion requires array of 2D/3D points';
- }
-
- return output;
- };
-
////////////////////////////////////////////////////////////////////////////
/**
* Get context specific renderer
@@ -24633,7 +26619,6 @@ geo.gl.vglRenderer = function (arg) {
var canvas = $(document.createElement('canvas'));
canvas.attr('class', 'webgl-canvas');
- m_this.canvas(canvas);
$(m_this.layer().node().get(0)).append(canvas);
m_viewer = vgl.viewer(canvas.get(0), arg.options);
m_viewer.init();
@@ -24643,6 +26628,11 @@ geo.gl.vglRenderer = function (arg) {
if (m_viewer.renderWindow().renderers().length > 0) {
m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length);
}
+ m_this.canvas(canvas);
+ /* Initialize the size of the renderer */
+ var map = m_this.layer().map(),
+ mapSize = map.size();
+ m_this._resize(0, 0, mapSize.width, mapSize.height);
return m_this;
};
@@ -24653,229 +26643,263 @@ geo.gl.vglRenderer = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
this._resize = function (x, y, w, h) {
- var vglRenderer = m_this.contextRenderer(),
- map = m_this.layer().map(),
- camera = vglRenderer.camera(),
- baseContextRenderer,
- baseCamera,
- renderWindow = m_viewer.renderWindow(),
- layer = m_this.layer(),
- baseLayer = layer.map().baseLayer(),
- focalPoint,
- position,
- zoom,
- newZ,
- mapCenter;
+ var renderWindow = m_viewer.renderWindow();
m_width = w;
m_height = h;
m_this.canvas().attr('width', w);
m_this.canvas().attr('height', h);
renderWindow.positionAndResize(x, y, w, h);
+
+ m_this._updateRendererCamera();
m_this._render();
- // Ignore if the base layer is not set yet
- if (!baseLayer || m_initialized) {
- return;
+ return m_this;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Render
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._render = function () {
+ if (m_renderAnimFrameRef === null) {
+ m_renderAnimFrameRef = window.requestAnimationFrame(function () {
+ m_viewer.render();
+ m_renderAnimFrameRef = null;
+ });
}
- m_initialized = true;
+ return m_this;
+ };
+
+ this._updateRendererCamera = function () {
+ var renderWindow = m_viewer.renderWindow(),
+ map = m_this.layer().map(),
+ camera = map.camera(),
+ view = camera.view,
+ proj = camera.projectionMatrix;
+ if (proj[15]) {
+ /* we want positive z to be closer to the camera, but webGL does the
+ * converse, so reverse the z coordinates. */
+ proj = mat4.scale(mat4.create(), proj, [1, 1, -1]);
+ }
+ /* A similar kluge as in the base camera class worldToDisplay4. With this,
+ * we can show z values from 0 to 1. */
+ proj = mat4.translate(mat4.create(), proj,
+ [0, 0, camera.constructor.bounds.far]);
+
+ renderWindow.renderers().forEach(function (renderer) {
+ var cam = renderer.camera();
+ cam.setViewMatrix(view);
+ cam.setProjectionMatrix(proj);
+ if (proj[1] || proj[2] || proj[3] || proj[4] || proj[6] || proj[7] ||
+ proj[8] || proj[9] || proj[11] || proj[15] !== 1 ||
+ (parseFloat(map.zoom().toFixed(6)) !==
+ parseFloat(map.zoom().toFixed(0)))) {
+ /* Don't align texels */
+ cam.viewAlignment = function () {
+ return null;
+ };
+ } else {
+ /* Set information for texel alignment. The rounding factors should
+ * probably be divided by window.devicePixelRatio. */
+ cam.viewAlignment = function () {
+ var align = {
+ roundx: 2.0 / camera.viewport.width,
+ roundy: 2.0 / camera.viewport.height
+ };
+ align.dx = (camera.viewport.width % 2) ? align.roundx * 0.5 : 0;
+ align.dy = (camera.viewport.height % 2) ? align.roundy * 0.5 : 0;
+ return align;
+ };
+ }
+ });
+ };
+
+ // Connect to interactor events
+ // Connect to pan event
+ m_this.layer().geoOn(geo.event.pan, function (evt) {
+ void(evt);
+ m_this._updateRendererCamera();
+ });
+
+ // Connect to zoom event
+ m_this.layer().geoOn(geo.event.zoom, function (evt) {
+ void(evt);
+ m_this._updateRendererCamera();
+ });
+
+ // Connect to parallelprojection event
+ m_this.layer().geoOn(geo.event.parallelprojection, function (evt) {
+ var vglRenderer = m_this.contextRenderer(),
+ camera,
+ layer = m_this.layer();
- // skip handling if the renderer is unconnected
- if (!vglRenderer || !vglRenderer.camera()) {
- console.log('Zoom event triggered on unconnected vgl renderer.');
+ if (evt.geo && evt.geo._triggeredBy !== layer) {
+ if (!vglRenderer || !vglRenderer.camera()) {
+ console.log('Parallel projection event triggered on unconnected VGL ' +
+ 'renderer.');
+ }
+ camera = vglRenderer.camera();
+ camera.setEnableParallelProjection(evt.parallelProjection);
+ m_this._updateRendererCamera();
}
+ });
- position = camera.position();
- zoom = map.zoom();
- newZ = camera.zoomToHeight(zoom, w, h);
+ return this;
+};
- // Assuming that baselayer will be a GL layer
- if (layer !== baseLayer) {
- baseContextRenderer = baseLayer.renderer().contextRenderer();
- baseCamera = baseContextRenderer.camera();
- position = baseCamera.position();
- focalPoint = baseCamera.focalPoint();
- camera.setPosition(position[0], position[1], position[2]);
- camera.setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
- } else {
- mapCenter = layer.toLocal(layer.map().center());
- focalPoint = camera.focalPoint();
- camera.setPosition(mapCenter.x, mapCenter.y, newZ);
- camera.setFocalPoint(mapCenter.x, mapCenter.y, focalPoint[2]);
+inherit(geo.gl.vglRenderer, geo.renderer);
+
+geo.registerRenderer('vgl', geo.gl.vglRenderer);
+
+geo.gl.tileLayer = function () {
+ 'use strict';
+ var m_this = this;
+
+ this._drawTile = function (tile) {
+ var bounds = this._tileBounds(tile),
+ level = tile.index.level || 0,
+ to = this._options.tileOffset(level);
+ var ul = this.fromLocal(this.fromLevel({
+ x: bounds.left - to.x, y: bounds.top - to.y
+ }, level), 0);
+ var lr = this.fromLocal(this.fromLevel({
+ x: bounds.right - to.x, y: bounds.bottom - to.y
+ }, level), 0);
+ /* Use a small z-value for layering the tile levels. */
+ tile.feature = m_this.createFeature(
+ 'plane', {drawOnAsyncResourceLoad: true})
+ .origin([ul.x, lr.y, level * 1e-7])
+ .upperLeft([ul.x, ul.y, level * 1e-7])
+ .lowerRight([lr.x, lr.y, level * 1e-7])
+ .style({image: tile._image});
+ tile.feature._update();
+ m_this.draw();
+ };
+
+ /* Remove the tile feature. */
+ this._remove = function (tile) {
+ if (tile.feature) {
+ m_this.deleteFeature(tile.feature);
+ tile.feature = null;
+ m_this.draw();
+ }
+ };
+
+ /* These functions don't need to do anything. */
+ this._getSubLayer = function () {};
+ this._updateSubLayer = function () {};
+};
+
+geo.registerLayerAdjustment('vgl', 'tile', geo.gl.tileLayer);
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of choroplethFeature
+ *
+ * @class
+ * @extends geo.choroplethFeature
+ * @returns {geo.gl.choroplethFeature}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.gl.choroplethFeature = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof geo.gl.choroplethFeature)) {
+ return new geo.gl.choroplethFeature(arg);
+ }
+ arg = arg || {};
+ geo.choroplethFeature.call(this, arg);
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ var m_this = this,
+ m_gl_polygons = null,
+ s_exit = this._exit,
+ s_init = this._init,
+ s_update = this._update;
+
+ /* Create the choropleth. This calls the base class to generate the contours,
+ * into the various gl uniforms and buffers.
+ */
+ function createGLChoropleth() {
+ return m_this.createChoropleth();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Build
+ *
+ * @override
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._build = function () {
+ m_this.buildTime().modified();
+ return (m_gl_polygons = createGLChoropleth());
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Update
+ *
+ * @override
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._update = function () {
+ s_update.call(m_this);
+ if (m_this.dataTime().getMTime() >= m_this.buildTime().getMTime() ||
+ m_this.updateTime().getMTime() <= m_this.getMTime()) {
+ m_this._wipePolygons();
+ m_this._build();
}
- camera.setParallelExtents({zoom: zoom});
-
- m_this._updateRendererCamera();
-
- return m_this;
+ m_this.updateTime().modified();
};
////////////////////////////////////////////////////////////////////////////
/**
- * Render
+ * Destroy Polygon Sub-Features
*/
////////////////////////////////////////////////////////////////////////////
- this._render = function () {
- m_viewer.render();
- return m_this;
+ this._wipePolygons = function () {
+ if (m_gl_polygons) {
+ m_gl_polygons.map(function (polygon) {
+ return polygon._exit();
+ });
+ }
+ m_gl_polygons = null;
};
- this._updateRendererCamera = function () {
- var vglRenderer = m_this.contextRenderer(),
- renderWindow = m_viewer.renderWindow(),
- camera = vglRenderer.camera(),
- pos, fp, cr, pe;
-
- vglRenderer.resetCameraClippingRange();
- pos = camera.position();
- fp = camera.focalPoint();
- cr = camera.clippingRange();
- pe = camera.parallelExtents();
- renderWindow.renderers().forEach(function (renderer) {
- var cam = renderer.camera();
-
- if (cam !== camera) {
- cam.setPosition(pos[0], pos[1], pos[2]);
- cam.setFocalPoint(fp[0], fp[1], fp[2]);
- cam.setClippingRange(cr[0], cr[1]);
- cam.setParallelExtents(pe);
- renderer.render();
- }
- });
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Destroy
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._exit = function () {
+ m_this._wipePolygons();
+ s_exit();
};
- // Connect to interactor events
- // Connect to pan event
- m_this.layer().geoOn(geo.event.pan, function (evt) {
- var vglRenderer = m_this.contextRenderer(),
- camera,
- focusPoint,
- centerDisplay,
- centerGeo,
- newCenterDisplay,
- newCenterGeo,
- renderWindow,
- layer = m_this.layer();
-
- if (evt.geo && evt.geo._triggeredBy !== layer) {
- // skip handling if the renderer is unconnected
- if (!vglRenderer || !vglRenderer.camera()) {
- console.log('Pan event triggered on unconnected VGL renderer.');
- }
-
- renderWindow = m_viewer.renderWindow();
- camera = vglRenderer.camera();
- focusPoint = renderWindow.focusDisplayPoint();
-
- // Calculate the center in display coordinates
- centerDisplay = [m_width / 2, m_height / 2, 0];
-
- // Calculate the center in world coordinates
- centerGeo = renderWindow.displayToWorld(
- centerDisplay[0],
- centerDisplay[1],
- focusPoint,
- vglRenderer
- );
-
- newCenterDisplay = [
- centerDisplay[0] + evt.screenDelta.x,
- centerDisplay[1] + evt.screenDelta.y
- ];
-
- newCenterGeo = renderWindow.displayToWorld(
- newCenterDisplay[0],
- newCenterDisplay[1],
- focusPoint,
- vglRenderer
- );
-
- camera.pan(
- centerGeo[0] - newCenterGeo[0],
- centerGeo[1] - newCenterGeo[1],
- centerGeo[2] - newCenterGeo[2]
- );
-
- evt.center = {
- x: newCenterGeo[0],
- y: newCenterGeo[1],
- z: newCenterGeo[2]
- };
-
- m_this._updateRendererCamera();
- }
- });
-
- // Connect to zoom event
- m_this.layer().geoOn(geo.event.zoom, function (evt) {
- var vglRenderer = m_this.contextRenderer(),
- camera,
- renderWindow,
- layer = m_this.layer(),
- center,
- dir,
- focusPoint,
- position,
- newZ;
-
- if (evt.geo && evt.geo._triggeredBy !== layer) {
- // skip handling if the renderer is unconnected
- if (!vglRenderer || !vglRenderer.camera()) {
- console.log('Zoom event triggered on unconnected vgl renderer.');
- }
-
- renderWindow = m_viewer.renderWindow();
- camera = vglRenderer.camera();
- focusPoint = camera.focalPoint();
- position = camera.position();
- var windowSize = renderWindow.windowSize();
- newZ = camera.zoomToHeight(evt.zoomLevel, windowSize[0], windowSize[1]);
-
- evt.pan = null;
- if (evt.screenPosition) {
- center = renderWindow.displayToWorld(
- evt.screenPosition.x,
- evt.screenPosition.y,
- focusPoint,
- vglRenderer
- );
- dir = [center[0] - position[0], center[1] - position[1], center[2] - position[2]];
- evt.center = layer.fromLocal({
- x: position[0] + dir[0] * (1 - newZ / position[2]),
- y: position[1] + dir[1] * (1 - newZ / position[2])
- });
- }
-
- camera.setPosition(position[0], position[1], newZ);
- camera.setParallelExtents({zoom: evt.zoomLevel});
-
- m_this._updateRendererCamera();
- }
- });
-
- // Connect to parallelprojection event
- m_this.layer().geoOn(geo.event.parallelprojection, function (evt) {
- var vglRenderer = m_this.contextRenderer(),
- camera,
- layer = m_this.layer();
-
- if (evt.geo && evt.geo._triggeredBy !== layer) {
- if (!vglRenderer || !vglRenderer.camera()) {
- console.log('Parallel projection event triggered on unconnected VGL ' +
- 'renderer.');
- }
- camera = vglRenderer.camera();
- camera.setEnableParallelProjection(evt.parallelProjection);
- m_this._updateRendererCamera();
- }
- });
-
+ this._init(arg);
return this;
};
-inherit(geo.gl.vglRenderer, geo.gl.renderer);
+inherit(geo.gl.choroplethFeature, geo.choroplethFeature);
-geo.registerRenderer('vgl', geo.gl.vglRenderer);
+// Now register it
+geo.registerFeature('vgl', 'choropleth', geo.gl.choroplethFeature);
/** @namespace */
geo.d3 = {};
@@ -24970,545 +26994,740 @@ inherit(geo.d3.object, geo.sceneObject);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of pointFeature
+ * Create a new instance of class d3Renderer
*
* @class
- * @extends geo.pointFeature
- * @extends geo.d3.object
- * @returns {geo.d3.pointFeature}
+ * @extends geo.renderer
+ * @returns {geo.d3.d3Renderer}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.d3.pointFeature = function (arg) {
+geo.d3.d3Renderer = function (arg) {
'use strict';
- if (!(this instanceof geo.d3.pointFeature)) {
- return new geo.d3.pointFeature(arg);
+
+ if (!(this instanceof geo.d3.d3Renderer)) {
+ return new geo.d3.d3Renderer(arg);
}
+ geo.renderer.call(this, arg);
+
+ var s_exit = this._exit;
+
+ geo.d3.object.call(this, arg);
+
arg = arg || {};
- geo.pointFeature.call(this, arg);
- geo.d3.object.call(this);
+
+ var m_this = this,
+ m_sticky = null,
+ m_features = {},
+ m_corners = null,
+ m_width = null,
+ m_height = null,
+ m_scale = 1,
+ m_dx = 0,
+ m_dy = 0,
+ m_svg = null,
+ m_defs = null;
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Set attributes to a d3 selection.
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ function setAttrs(select, attrs) {
+ var key;
+ for (key in attrs) {
+ if (attrs.hasOwnProperty(key)) {
+ select.attr(key, attrs[key]);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Meta functions for converting from geojs styles to d3.
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._convertColor = function (f, g) {
+ f = geo.util.ensureFunction(f);
+ g = g || function () { return true; };
+ return function () {
+ var c = 'none';
+ if (g.apply(m_this, arguments)) {
+ c = f.apply(m_this, arguments);
+ if (c.hasOwnProperty('r') &&
+ c.hasOwnProperty('g') &&
+ c.hasOwnProperty('b')) {
+ c = d3.rgb(255 * c.r, 255 * c.g, 255 * c.b);
+ }
+ }
+ return c;
+ };
+ };
+
+ this._convertPosition = function (f) {
+ f = geo.util.ensureFunction(f);
+ return function () {
+ return m_this.layer().map().worldToDisplay(f.apply(m_this, arguments));
+ };
+ };
+
+ this._convertScale = function (f) {
+ f = geo.util.ensureFunction(f);
+ return function () {
+ return f.apply(m_this, arguments) / m_scale;
+ };
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Set styles to a d3 selection. Ignores unkown style keys.
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ function setStyles(select, styles) {
+ /* jshint validthis:true */
+ var key, k, f;
+ function fillFunc() {
+ if (styles.fill.apply(m_this, arguments)) {
+ return null;
+ } else {
+ return 'none';
+ }
+ }
+ function strokeFunc() {
+ if (styles.stroke.apply(m_this, arguments)) {
+ return null;
+ } else {
+ return 'none';
+ }
+ }
+ for (key in styles) {
+ if (styles.hasOwnProperty(key)) {
+ f = null;
+ k = null;
+ if (key === 'strokeColor') {
+ k = 'stroke';
+ f = m_this._convertColor(styles[key], styles.stroke);
+ } else if (key === 'stroke' && styles[key]) {
+ k = 'stroke';
+ f = strokeFunc;
+ } else if (key === 'strokeWidth') {
+ k = 'stroke-width';
+ f = m_this._convertScale(styles[key]);
+ } else if (key === 'strokeOpacity') {
+ k = 'stroke-opacity';
+ f = styles[key];
+ } else if (key === 'fillColor') {
+ k = 'fill';
+ f = m_this._convertColor(styles[key], styles.fill);
+ } else if (key === 'fill' && !styles.hasOwnProperty('fillColor')) {
+ k = 'fill';
+ f = fillFunc;
+ } else if (key === 'fillOpacity') {
+ k = 'fill-opacity';
+ f = styles[key];
+ }
+ if (k) {
+ select.style(k, f);
+ }
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get the svg group element associated with this renderer instance, or of a
+ * group within the render instance.
+ *
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ function getGroup(parentId) {
+ if (parentId) {
+ return m_svg.select('.group-' + parentId);
+ }
+ return m_svg.select('.group-' + m_this._d3id());
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Set the initial lat-lon coordinates of the map view.
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ function initCorners() {
+ var layer = m_this.layer(),
+ map = layer.map(),
+ width = m_this.layer().map().size().width,
+ height = m_this.layer().map().size().height;
+
+ m_width = width;
+ m_height = height;
+ if (!m_width || !m_height) {
+ throw 'Map layer has size 0';
+ }
+ m_corners = {
+ upperLeft: map.displayToGcs({'x': 0, 'y': 0}, null),
+ lowerRight: map.displayToGcs({'x': width, 'y': height}, null)
+ };
+ }
////////////////////////////////////////////////////////////////////////////
/**
+ * Set the translation, scale, and zoom for the current view.
+ * @note rotation not yet supported
* @private
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init,
- s_update = this._update,
- m_buildTime = geo.timestamp(),
- m_style = {},
- m_sticky;
+ this._setTransform = function () {
+ if (!m_corners) {
+ initCorners();
+ }
+
+ if (!m_sticky) {
+ return;
+ }
+
+ var layer = m_this.layer(),
+ map = layer.map(),
+ upperLeft = map.gcsToDisplay(m_corners.upperLeft, null),
+ lowerRight = map.gcsToDisplay(m_corners.lowerRight, null),
+ group = getGroup(),
+ canvas = m_this.canvas(),
+ dx, dy, scale;
+
+ if (canvas.attr('scale') !== null) {
+ scale = canvas.attr('scale') || 1;
+ dx = (canvas.attr('dx') || 0) * scale;
+ dy = (canvas.attr('dy') || 0) * scale;
+ dx += map.size().width / 2;
+ dy += map.size().height / 2;
+ } else {
+ // calculate the translation
+ dx = upperLeft.x;
+ dy = upperLeft.y;
+ scale = (lowerRight.y - upperLeft.y) / m_height;
+ }
+
+ // set the group transform property
+ group.attr('transform', 'matrix(' + [scale, 0, 0, scale, dx, dy].join() + ')');
+
+ // set internal variables
+ m_scale = scale;
+ m_dx = dx;
+ m_dy = dy;
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Convert from screen pixel coordinates to the local coordinate system
+ * in the SVG group element taking into account the transform.
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
- m_sticky = m_this.layer().sticky();
- return m_this;
+ this.baseToLocal = function (pt) {
+ return {
+ x: (pt.x - m_dx) / m_scale,
+ y: (pt.y - m_dy) / m_scale
+ };
};
////////////////////////////////////////////////////////////////////////////
/**
- * Build
- *
- * @override
+ * Convert from the local coordinate system in the SVG group element
+ * to screen pixel coordinates.
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this._build = function () {
- var data = m_this.data(),
- s_style = m_this.style.get(),
- m_renderer = m_this.renderer(),
- pos_func = m_this.position();
-
- // call super-method
- s_update.call(m_this);
-
- // default to empty data array
- if (!data) { data = []; }
-
- // fill in d3 renderer style object defaults
- m_style.id = m_this._d3id();
- m_style.data = data;
- m_style.append = 'circle';
- m_style.attributes = {
- r: m_renderer._convertScale(s_style.radius),
- cx: function (d) {
- return m_renderer.worldToDisplay(pos_func(d)).x;
- },
- cy: function (d) {
- return m_renderer.worldToDisplay(pos_func(d)).y;
- }
+ this.localToBase = function (pt) {
+ return {
+ x: pt.x * m_scale + m_dx,
+ y: pt.y * m_scale + m_dy
};
- m_style.style = s_style;
- m_style.classes = ['d3PointFeature'];
-
- // pass to renderer to draw
- m_this.renderer()._drawFeatures(m_style);
-
- // update time stamps
- m_buildTime.modified();
- m_this.updateTime().modified();
- return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update
- *
- * @override
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this._update = function () {
- s_update.call(m_this);
+ this._init = function (arg) {
+ if (!m_this.canvas()) {
+ var canvas;
+ arg.widget = arg.widget || false;
- if (m_this.getMTime() >= m_buildTime.getMTime()) {
- m_this._build();
- }
+ if ('d3Parent' in arg) {
+ m_svg = d3.select(arg.d3Parent).append('svg');
+ } else {
+ m_svg = d3.select(m_this.layer().node().get(0)).append('svg');
+ }
- return m_this;
- };
+ // create a global svg definitions element
+ m_defs = m_svg.append('defs');
- this._init(arg);
- return this;
-};
+ var shadow = m_defs
+ .append('filter')
+ .attr('id', 'geo-highlight')
+ .attr('x', '-100%')
+ .attr('y', '-100%')
+ .attr('width', '300%')
+ .attr('height', '300%');
+ shadow
+ .append('feMorphology')
+ .attr('operator', 'dilate')
+ .attr('radius', 2)
+ .attr('in', 'SourceAlpha')
+ .attr('result', 'dilateOut');
+ shadow
+ .append('feGaussianBlur')
+ .attr('stdDeviation', 5)
+ .attr('in', 'dilateOut')
+ .attr('result', 'blurOut');
+ shadow
+ .append('feColorMatrix')
+ .attr('type', 'matrix')
+ .attr('values', '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0')
+ .attr('in', 'blurOut')
+ .attr('result', 'invertOut');
+ shadow
+ .append('feBlend')
+ .attr('in', 'SourceGraphic')
+ .attr('in2', 'invertOut')
+ .attr('mode', 'normal');
-inherit(geo.d3.pointFeature, geo.pointFeature);
+ if (!arg.widget) {
+ canvas = m_svg.append('g');
+ }
-// Now register it
-geo.registerFeature('d3', 'point', geo.d3.pointFeature);
+ shadow = m_defs.append('filter')
+ .attr('id', 'geo-blur')
+ .attr('x', '-100%')
+ .attr('y', '-100%')
+ .attr('width', '300%')
+ .attr('height', '300%');
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class lineFeature
- *
- * @class
- * @extends geo.lineFeature
- * @extends geo.d3.object
- * @returns {geo.d3.lineFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.d3.lineFeature = function (arg) {
- 'use strict';
- if (!(this instanceof geo.d3.lineFeature)) {
- return new geo.d3.lineFeature(arg);
- }
- arg = arg || {};
- geo.lineFeature.call(this, arg);
- geo.d3.object.call(this);
+ shadow
+ .append('feGaussianBlur')
+ .attr('stdDeviation', 20)
+ .attr('in', 'SourceGraphic');
+
+ m_sticky = m_this.layer().sticky();
+ m_svg.attr('class', m_this._d3id());
+ m_svg.attr('width', m_this.layer().node().width());
+ m_svg.attr('height', m_this.layer().node().height());
+
+ if (!arg.widget) {
+ canvas.attr('class', 'group-' + m_this._d3id());
+
+ m_this.canvas(canvas);
+ } else {
+ m_this.canvas(m_svg);
+ }
+ }
+ m_this._setTransform();
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Get API used by the renderer
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init,
- m_buildTime = geo.timestamp(),
- s_update = this._update;
+ this.api = function () {
+ return 'd3';
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Return the current scaling factor to build features that shouldn't
+ * change size during zooms. For example:
+ *
+ * selection.append('circle')
+ * .attr('r', r0 / renderer.scaleFactor());
+ *
+ * This will create a circle element with radius r0 independent of the
+ * current zoom level.
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
- return m_this;
+ this.scaleFactor = function () {
+ return m_scale;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Build
- *
- * @override
+ * Handle resize event
*/
////////////////////////////////////////////////////////////////////////////
- this._build = function () {
- var data = m_this.data() || [],
- s_style = m_this.style(),
- m_renderer = m_this.renderer(),
- pos_func = m_this.position(),
- line = d3.svg.line()
- .x(function (d) { return m_renderer.worldToDisplay(d).x; })
- .y(function (d) { return m_renderer.worldToDisplay(d).y; });
-
- s_update.call(m_this);
- s_style.fill = function () { return false; };
-
- data.forEach(function (item, idx) {
- var m_style;
- var ln = m_this.line()(item, idx);
-
- var style = {}, key;
- function wrapStyle(func) {
- if (geo.util.isFunction(func)) {
- return function () {
- return func(ln[0], 0, item, idx);
- };
- } else {
- return func;
- }
- }
- for (key in s_style) {
- if (s_style.hasOwnProperty(key)) {
- style[key] = wrapStyle(s_style[key]);
- }
- }
-
- // item is an object representing a single line
- // m_this.line()(item) is an array of coordinates
- m_style = {
- data: [ln.map(function (d, i) { return pos_func(d, i, item, idx);})],
- append: 'path',
- attributes: {
- d: line
- },
- id: m_this._d3id() + idx,
- classes: ['d3LineFeature', 'd3SubLine-' + idx],
- style: style
- };
-
- m_renderer._drawFeatures(m_style);
- });
-
- m_buildTime.modified();
- m_this.updateTime().modified();
- return m_this;
+ this._resize = function (x, y, w, h) {
+ if (!m_corners) {
+ initCorners();
+ }
+ m_svg.attr('width', w);
+ m_svg.attr('height', h);
+ m_this._setTransform();
+ m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true);
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update
- *
- * @override
+ * Update noop for geo.d3.object api.
*/
////////////////////////////////////////////////////////////////////////////
this._update = function () {
- s_update.call(m_this);
-
- if (m_this.getMTime() >= m_buildTime.getMTime()) {
- m_this._build();
- }
-
- return m_this;
};
- this._init(arg);
- return this;
-};
-
-inherit(geo.d3.lineFeature, geo.lineFeature);
-
-geo.registerFeature('d3', 'line', geo.d3.lineFeature);
-
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new instance of class pathFeature
- *
- * @class
- * @extends geo.pathFeature
- * @extends geo.d3.object
- * @returns {geo.d3.pathFeature}
- */
-//////////////////////////////////////////////////////////////////////////////
-geo.d3.pathFeature = function (arg) {
- 'use strict';
- if (!(this instanceof geo.d3.pathFeature)) {
- return new geo.d3.pathFeature(arg);
- }
- arg = arg || {};
- geo.pathFeature.call(this, arg);
- geo.d3.object.call(this);
-
////////////////////////////////////////////////////////////////////////////
/**
- * @private
+ * Exit
*/
////////////////////////////////////////////////////////////////////////////
- var m_this = this,
- s_init = this._init,
- m_buildTime = geo.timestamp(),
- s_update = this._update,
- m_style = {};
-
- m_style.style = {};
+ this._exit = function () {
+ m_features = {};
+ m_this.canvas().remove();
+ s_exit();
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
+ * Get the definitions dom element for the layer
+ * @protected
*/
////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg);
- return m_this;
+ this._definitions = function () {
+ return m_defs;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Build
+ * Create a new feature element from an object that describes the feature
+ * attributes. To be called from feature classes only.
*
- * @override
+ * Input:
+ * {
+ * id: A unique string identifying the feature.
+ * data: Array of data objects used in a d3 data method.
+ * index: A function that returns a unique id for each data element.
+ * style: An object containing element CSS styles.
+ * attributes: An object containing element attributes.
+ * classes: An array of classes to add to the elements.
+ * append: The element type as used in d3 append methods.
+ * parentId: If set, the group ID of the parent element.
+ * }
*/
////////////////////////////////////////////////////////////////////////////
- this._build = function () {
- var data = m_this.data() || [],
- s_style = m_this.style(),
- m_renderer = m_this.renderer(),
- tmp, diag;
- s_update.call(m_this);
-
- diag = function (d) {
- var p = {
- source: d.source,
- target: d.target
- };
- return d3.svg.diagonal()(p);
- };
- tmp = [];
- data.forEach(function (d, i) {
- var src, trg;
- if (i < data.length - 1) {
- src = d;
- trg = data[i + 1];
- tmp.push({
- source: m_renderer.worldToDisplay(src),
- target: m_renderer.worldToDisplay(trg)
- });
- }
- });
- m_style.data = tmp;
- m_style.attributes = {
- d: diag
+ this._drawFeatures = function (arg) {
+ m_features[arg.id] = {
+ data: arg.data,
+ index: arg.dataIndex,
+ style: arg.style,
+ attributes: arg.attributes,
+ classes: arg.classes,
+ append: arg.append,
+ parentId: arg.parentId
};
+ return m_this.__render(arg.id, arg.parentId);
+ };
- m_style.id = m_this._d3id();
- m_style.append = 'path';
- m_style.classes = ['d3PathFeature'];
- m_style.style = $.extend({
- 'fill': function () { return false; },
- 'fillColor': function () { return { r: 0, g: 0, b: 0 }; }
- }, s_style);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Updates a feature by performing a d3 data join. If no input id is
+ * provided then this method will update all features.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.__render = function (id, parentId) {
+ var key;
+ if (id === undefined) {
+ for (key in m_features) {
+ if (m_features.hasOwnProperty(key)) {
+ m_this.__render(key);
+ }
+ }
+ return m_this;
+ }
+ var data = m_features[id].data,
+ index = m_features[id].index,
+ style = m_features[id].style,
+ attributes = m_features[id].attributes,
+ classes = m_features[id].classes,
+ append = m_features[id].append,
+ selection = m_this.select(id, parentId).data(data, index);
+ selection.enter().append(append);
+ selection.exit().remove();
+ setAttrs(selection, attributes);
+ selection.attr('class', classes.concat([id]).join(' '));
+ setStyles(selection, style);
+ return m_this;
+ };
- m_this.renderer()._drawFeatures(m_style);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Returns a d3 selection for the given feature id.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.select = function (id, parentId) {
+ return getGroup(parentId).selectAll('.' + id);
+ };
- m_buildTime.modified();
- m_this.updateTime().modified();
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Removes a feature from the layer.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._removeFeature = function (id) {
+ m_this.select(id).remove();
+ delete m_features[id];
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Update
- *
- * @override
- */
+ * Override draw method to do nothing.
+ */
////////////////////////////////////////////////////////////////////////////
- this._update = function () {
- s_update.call(m_this);
+ this.draw = function () {
+ };
- if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) {
- m_this._build();
- }
+ // connect to pan event
+ this.layer().geoOn(geo.event.pan, m_this._setTransform);
- return m_this;
- };
+ // connect to zoom event
+ this.layer().geoOn(geo.event.zoom, function () {
+ m_this._setTransform();
+ m_this.__render();
+ m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true);
+ });
+
+ this.layer().geoOn(geo.event.resize, function (event) {
+ m_this._resize(event.x, event.y, event.width, event.height);
+ });
this._init(arg);
return this;
};
-inherit(geo.d3.pathFeature, geo.pathFeature);
+inherit(geo.d3.d3Renderer, geo.renderer);
-geo.registerFeature('d3', 'path', geo.d3.pathFeature);
+geo.registerRenderer('d3', geo.d3.d3Renderer);
-/**
- * @class
- * @extends geo.graphFeature
- */
-geo.d3.graphFeature = function (arg) {
+geo.d3.tileLayer = function () {
'use strict';
+ var m_this = this,
+ s_update = this._update,
+ s_init = this._init;
- var m_this = this;
+ this._drawTile = function (tile) {
+ var bounds = m_this._tileBounds(tile),
+ parentNode = m_this._getSubLayer(tile.index.level);
+ tile.feature = m_this.createFeature(
+ 'plane', {drawOnAsyncResourceLoad: true})
+ .origin([bounds.left, bounds.top])
+ .upperLeft([bounds.left, bounds.top])
+ .lowerRight([bounds.right, bounds.bottom])
+ .style({
+ image: tile._url,
+ opacity: 1,
+ reference: tile.toString(),
+ parentId: parentNode.attr('data-tile-layer-id')
+ });
+ tile.feature._update();
+ m_this.draw();
+ };
- if (!(this instanceof geo.d3.graphFeature)) {
- return new geo.d3.graphFeature(arg);
- }
- geo.graphFeature.call(this, arg);
+ /**
+ * Return the DOM eleement containing a level specific
+ * layer. This will create the element if it doesn't
+ * already exist.
+ * @param {number} level The zoom level of the layer to fetch
+ * @return {DOM}
+ */
+ this._getSubLayer = function (level) {
+ var node = m_this.canvas().select(
+ 'g[data-tile-layer="' + level.toFixed() + '"]');
+ if (node.empty()) {
+ node = m_this.canvas().append('g');
+ var id = geo.d3.uniqueID();
+ node.classed('group-' + id, true);
+ node.classed('geo-tile-layer', true);
+ node.attr('data-tile-layer', level.toFixed());
+ node.attr('data-tile-layer-id', id);
+ }
+ return node;
+ };
- ////////////////////////////////////////////////////////////////////////////
/**
- * Returns a d3 selection for the graph elements
- */
- ////////////////////////////////////////////////////////////////////////////
- this.select = function () {
- var renderer = m_this.renderer(),
- selection = {},
- node = m_this.nodeFeature(),
- links = m_this.linkFeatures();
- selection.nodes = renderer.select(node._d3id());
- selection.links = links.map(function (link) {
- return renderer.select(link._d3id());
+ * Set sublayer transforms to align them with the given zoom level.
+ * @param {number} level The target zoom level
+ */
+ this._updateSubLayers = function (level) {
+ $.each(m_this.canvas().selectAll('.geo-tile-layer')[0], function (idx, el) {
+ var layer = parseInt($(el).attr('data-tile-layer'));
+ el = m_this._getSubLayer(layer);
+ var scale = Math.pow(2, level - layer);
+ el.attr('transform', 'matrix(' + [scale, 0, 0, scale, 0, 0].join() + ')');
});
- return selection;
};
- return this;
-};
+ /* Initialize the tile layer. This creates a series of sublayers so that
+ * the different layers will stack in the proper order.
+ */
+ this._init = function () {
+ var sublayer;
-inherit(geo.d3.graphFeature, geo.graphFeature);
+ s_init.apply(m_this, arguments);
+ for (sublayer = 0; sublayer <= m_this._options.maxLevel; sublayer += 1) {
+ m_this._getSubLayer(sublayer);
+ }
+ };
-geo.registerFeature('d3', 'graph', geo.d3.graphFeature);
+ /* When update is called, apply the transform to our renderer. */
+ this._update = function () {
+ s_update.apply(m_this, arguments);
+ m_this.renderer()._setTransform();
+ };
+
+ /* Remove both the tile feature and an internal image element. */
+ this._remove = function (tile) {
+ if (tile.feature) {
+ m_this.deleteFeature(tile.feature);
+ tile.feature = null;
+ }
+ if (tile.image) {
+ $(tile.image).remove();
+ }
+ };
+};
+
+geo.registerLayerAdjustment('d3', 'tile', geo.d3.tileLayer);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a plane feature given a lower left corner point
- * and and upper right corner point
- *
- * *CURRENTLY BROKEN*
+ * Create a new instance of pointFeature
*
* @class
- * @extends geo.planeFeature
- * @param lowerleft
- * @param upperright
- * @returns {geo.d3.planeFeature}
+ * @extends geo.pointFeature
+ * @extends geo.d3.object
+ * @returns {geo.d3.pointFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.d3.planeFeature = function (arg) {
+geo.d3.pointFeature = function (arg) {
'use strict';
- if (!(this instanceof geo.d3.planeFeature)) {
- return new geo.d3.planeFeature(arg);
+ if (!(this instanceof geo.d3.pointFeature)) {
+ return new geo.d3.pointFeature(arg);
}
- geo.planeFeature.call(this, arg);
+ arg = arg || {};
+ geo.pointFeature.call(this, arg);
geo.d3.object.call(this);
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @private
+ */
+ ////////////////////////////////////////////////////////////////////////////
var m_this = this,
- m_style = {},
- s_update = this._update,
s_init = this._init,
- m_buildTime = geo.timestamp();
+ s_update = this._update,
+ m_buildTime = geo.timestamp(),
+ m_style = {},
+ m_sticky;
- //////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
/**
- * Normalize a coordinate as an object {x: ..., y: ...}
- *
- * @private
- * @returns {Object}
+ * Initialize
*/
- //////////////////////////////////////////////////////////////////////////////
- function normalize(pt) {
- if (Array.isArray(pt)) {
- return {
- x: pt[0],
- y: pt[1]
- };
- }
- return pt;
- }
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+ m_sticky = m_this.layer().sticky();
+ return m_this;
+ };
- //////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
/**
- * Build the feature object and pass to the renderer for drawing.
+ * Build
*
- * @private
- * @returns {geo.d3.planeFeature}
+ * @override
*/
- //////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
this._build = function () {
- var origin = normalize(m_this.origin()),
- ul = normalize(m_this.upperLeft()),
- lr = normalize(m_this.lowerRight()),
- renderer = m_this.renderer(),
- s = m_this.style();
+ var data = m_this.data(),
+ s_style = m_this.style.get(),
+ m_renderer = m_this.renderer(),
+ pos_func = m_this.position();
- delete s.fill_color;
- delete s.color;
- delete s.opacity;
- if (!s.screenCoordinates) {
- origin = renderer.worldToDisplay(origin);
- ul = renderer.worldToDisplay(ul);
- lr = renderer.worldToDisplay(lr);
- }
+ // call super-method
+ s_update.call(m_this);
+
+ // default to empty data array
+ if (!data) { data = []; }
+
+ // fill in d3 renderer style object defaults
m_style.id = m_this._d3id();
- m_style.style = s;
+ m_style.data = data;
+ m_style.append = 'circle';
m_style.attributes = {
- x: ul.x,
- y: ul.y,
- width: lr.x - origin.x,
- height: origin.y - ul.y
+ r: m_renderer._convertScale(s_style.radius),
+ cx: function (d) {
+ return m_this.featureGcsToDisplay(pos_func(d)).x;
+ },
+ cy: function (d) {
+ return m_this.featureGcsToDisplay(pos_func(d)).y;
+ }
};
- m_style.append = 'rect';
- m_style.data = [0];
- m_style.classes = ['d3PlaneFeature'];
+ m_style.style = s_style;
+ m_style.classes = ['d3PointFeature'];
- renderer._drawFeatures(m_style);
+ // pass to renderer to draw
+ m_this.renderer()._drawFeatures(m_style);
+
+ // update time stamps
m_buildTime.modified();
+ m_this.updateTime().modified();
return m_this;
};
- //////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
/**
- * Redraw the plane feature if necessary.
+ * Update
*
- * @private
- * @returns {geo.d3.planeFeature}
+ * @override
*/
- //////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
this._update = function () {
s_update.call(m_this);
- if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) {
+ if (m_this.getMTime() >= m_buildTime.getMTime()) {
m_this._build();
}
- return m_this;
- };
- //////////////////////////////////////////////////////////////////////////////
- /**
- * Initializes the plane feature style (over-riding the parent default).
- *
- * @private
- * @returns {geo.d3.planeFeature}
- */
- //////////////////////////////////////////////////////////////////////////////
- this._init = function (arg) {
- s_init.call(m_this, arg || {});
- m_this.style({
- stroke: function () { return false; },
- fill: function () { return true; },
- fillColor: function () { return {r: 0.3, g: 0.3, b: 0.3}; },
- fillOpacity: function () { return 0.5; }
- });
return m_this;
};
- this._init();
+ this._init(arg);
return this;
};
-inherit(geo.d3.planeFeature, geo.planeFeature);
+inherit(geo.d3.pointFeature, geo.pointFeature);
-geo.registerFeature('d3', 'plane', geo.d3.planeFeature);
+// Now register it
+geo.registerFeature('d3', 'point', geo.d3.pointFeature);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of vectorFeature
+ * Create a new instance of class lineFeature
*
* @class
- * @extends geo.vectorFeature
+ * @extends geo.lineFeature
* @extends geo.d3.object
- * @returns {geo.d3.vectorFeature}
+ * @returns {geo.d3.lineFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.d3.vectorFeature = function (arg) {
+geo.d3.lineFeature = function (arg) {
'use strict';
- if (!(this instanceof geo.d3.vectorFeature)) {
- return new geo.d3.vectorFeature(arg);
+ if (!(this instanceof geo.d3.lineFeature)) {
+ return new geo.d3.lineFeature(arg);
}
arg = arg || {};
- geo.vectorFeature.call(this, arg);
+ geo.lineFeature.call(this, arg);
geo.d3.object.call(this);
////////////////////////////////////////////////////////////////////////////
@@ -25518,151 +27737,74 @@ geo.d3.vectorFeature = function (arg) {
////////////////////////////////////////////////////////////////////////////
var m_this = this,
s_init = this._init,
- s_exit = this._exit,
- s_update = this._update,
m_buildTime = geo.timestamp(),
- m_style = {},
- m_sticky;
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Generate a unique ID for a marker definition
- * @private
- * @param {object} d Unused datum (for d3 compat)
- * @param {number} i The marker index
- */
- ////////////////////////////////////////////////////////////////////////////
- function markerID(d, i) {
- return m_this._d3id() + '_marker_' + i;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Add marker styles for vector arrows.
- * @private
- * @param {object[]} data The vector data array
- * @param {function} stroke The stroke accessor
- * @param {function} opacity The opacity accessor
- */
- ////////////////////////////////////////////////////////////////////////////
- function updateMarkers(data, stroke, opacity) {
-
- var renderer = m_this.renderer();
- var sel = m_this.renderer()._definitions()
- .selectAll('marker.geo-vector')
- .data(data);
- sel.enter()
- .append('marker')
- .attr('id', markerID)
- .attr('class', 'geo-vector')
- .attr('viewBox', '0 0 10 10')
- .attr('refX', '1')
- .attr('refY', '5')
- .attr('markerWidth', '5')
- .attr('markerHeight', '5')
- .attr('orient', 'auto')
- .append('path')
- .attr('d', 'M 0 0 L 10 5 L 0 10 z');
-
- sel.exit().remove();
-
- sel.style('stroke', renderer._convertColor(stroke))
- .style('fill', renderer._convertColor(stroke))
- .style('opacity', opacity);
- }
+ s_update = this._update;
////////////////////////////////////////////////////////////////////////////
/**
* Initialize
- * @protected
*/
////////////////////////////////////////////////////////////////////////////
this._init = function (arg) {
s_init.call(m_this, arg);
- m_sticky = m_this.layer().sticky();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
* Build
- * @protected
+ *
+ * @override
*/
////////////////////////////////////////////////////////////////////////////
this._build = function () {
- var data = m_this.data(),
- s_style = m_this.style.get(),
+ var data = m_this.data() || [],
+ s_style = m_this.style(),
m_renderer = m_this.renderer(),
- orig_func = m_this.origin(),
- size_func = m_this.delta(),
- cache = [],
- scale = m_this.style('scale'),
- max = Number.NEGATIVE_INFINITY;
+ pos_func = m_this.position(),
+ line = d3.svg.line()
+ .x(function (d) { return m_this.featureGcsToDisplay(d).x; })
+ .y(function (d) { return m_this.featureGcsToDisplay(d).y; });
- // call super-method
s_update.call(m_this);
-
- // default to empty data array
- if (!data) { data = []; }
-
- // cache the georeferencing
- cache = data.map(function (d, i) {
- var origin = m_renderer.worldToDisplay(orig_func(d, i)),
- delta = size_func(d, i);
- max = Math.max(max, delta.x * delta.x + delta.y * delta.y);
- return {
- x1: origin.x,
- y1: origin.y,
- dx: delta.x,
- dy: -delta.y
- };
- });
-
- max = Math.sqrt(max);
- if (!scale) {
- scale = 75 / max;
- }
-
- function getScale() {
- return scale / m_renderer.scaleFactor();
- }
-
- // fill in d3 renderer style object defaults
- m_style.id = m_this._d3id();
- m_style.data = data;
- m_style.append = 'line';
- m_style.attributes = {
- x1: function (d, i) {
- return cache[i].x1;
- },
- y1: function (d, i) {
- return cache[i].y1;
- },
- x2: function (d, i) {
- return cache[i].x1 + getScale() * cache[i].dx;
- },
- y2: function (d, i) {
- return cache[i].y1 + getScale() * cache[i].dy;
- },
- 'marker-end': function (d, i) {
- return 'url(#' + markerID(d, i) + ')';
+ s_style.fill = function () { return false; };
+
+ data.forEach(function (item, idx) {
+ var m_style;
+ var ln = m_this.line()(item, idx);
+
+ var style = {}, key;
+ function wrapStyle(func) {
+ if (geo.util.isFunction(func)) {
+ return function () {
+ return func(ln[0], 0, item, idx);
+ };
+ } else {
+ return func;
+ }
+ }
+ for (key in s_style) {
+ if (s_style.hasOwnProperty(key)) {
+ style[key] = wrapStyle(s_style[key]);
+ }
}
- };
- m_style.style = {
- stroke: function () { return true; },
- strokeColor: s_style.strokeColor,
- strokeWidth: s_style.strokeWidth,
- strokeOpacity: s_style.strokeOpacity
- };
- m_style.classes = ['d3VectorFeature'];
- // Add markers to the defition list
- updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity);
+ // item is an object representing a single line
+ // m_this.line()(item) is an array of coordinates
+ m_style = {
+ data: [ln.map(function (d, i) { return pos_func(d, i, item, idx);})],
+ append: 'path',
+ attributes: {
+ d: line
+ },
+ id: m_this._d3id() + idx,
+ classes: ['d3LineFeature', 'd3SubLine-' + idx],
+ style: style
+ };
- // pass to renderer to draw
- m_this.renderer()._drawFeatures(m_style);
+ m_renderer._drawFeatures(m_style);
+ });
- // update time stamps
m_buildTime.modified();
m_this.updateTime().modified();
return m_this;
@@ -25671,7 +27813,8 @@ geo.d3.vectorFeature = function (arg) {
////////////////////////////////////////////////////////////////////////////
/**
* Update
- * @protected
+ *
+ * @override
*/
////////////////////////////////////////////////////////////////////////////
this._update = function () {
@@ -25679,584 +27822,532 @@ geo.d3.vectorFeature = function (arg) {
if (m_this.getMTime() >= m_buildTime.getMTime()) {
m_this._build();
- } else {
- updateMarkers(
- m_style.data,
- m_style.style.strokeColor,
- m_style.style.strokeOpacity
- );
}
return m_this;
};
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Exit
- * @protected
- */
- ////////////////////////////////////////////////////////////////////////////
- this._exit = function () {
- s_exit.call(m_this);
- m_style = {};
- updateMarkers([], null, null);
- };
-
this._init(arg);
return this;
};
-inherit(geo.d3.vectorFeature, geo.vectorFeature);
+inherit(geo.d3.lineFeature, geo.lineFeature);
-// Now register it
-geo.registerFeature('d3', 'vector', geo.d3.vectorFeature);
+geo.registerFeature('d3', 'line', geo.d3.lineFeature);
//////////////////////////////////////////////////////////////////////////////
/**
- * Create a new instance of class d3Renderer
+ * Create a new instance of class pathFeature
*
* @class
- * @extends geo.renderer
- * @returns {geo.d3.d3Renderer}
+ * @extends geo.pathFeature
+ * @extends geo.d3.object
+ * @returns {geo.d3.pathFeature}
*/
//////////////////////////////////////////////////////////////////////////////
-geo.d3.d3Renderer = function (arg) {
+geo.d3.pathFeature = function (arg) {
'use strict';
-
- if (!(this instanceof geo.d3.d3Renderer)) {
- return new geo.d3.d3Renderer(arg);
+ if (!(this instanceof geo.d3.pathFeature)) {
+ return new geo.d3.pathFeature(arg);
}
- geo.renderer.call(this, arg);
-
- var s_exit = this._exit;
-
- geo.d3.object.call(this, arg);
-
arg = arg || {};
-
- var m_this = this,
- m_sticky = null,
- m_features = {},
- m_corners = null,
- m_width = null,
- m_height = null,
- m_scale = 1,
- m_dx = 0,
- m_dy = 0,
- m_svg = null,
- m_defs = null;
+ geo.pathFeature.call(this, arg);
+ geo.d3.object.call(this);
////////////////////////////////////////////////////////////////////////////
/**
- * Set attributes to a d3 selection.
* @private
*/
////////////////////////////////////////////////////////////////////////////
- function setAttrs(select, attrs) {
- var key;
- for (key in attrs) {
- if (attrs.hasOwnProperty(key)) {
- select.attr(key, attrs[key]);
- }
- }
- }
+ var m_this = this,
+ s_init = this._init,
+ m_buildTime = geo.timestamp(),
+ s_update = this._update,
+ m_style = {};
+
+ m_style.style = {};
////////////////////////////////////////////////////////////////////////////
/**
- * Meta functions for converting from geojs styles to d3.
- * @private
+ * Initialize
*/
////////////////////////////////////////////////////////////////////////////
- this._convertColor = function (f, g) {
- f = geo.util.ensureFunction(f);
- g = g || function () { return true; };
- return function () {
- var c = 'none';
- if (g.apply(this, arguments)) {
- c = f.apply(this, arguments);
- if (c.hasOwnProperty('r') &&
- c.hasOwnProperty('g') &&
- c.hasOwnProperty('b')) {
- c = d3.rgb(255 * c.r, 255 * c.g, 255 * c.b);
- }
- }
- return c;
- };
- };
-
- this._convertPosition = function (f) {
- f = geo.util.ensureFunction(f);
- return function () {
- return m_this.worldToDisplay(f.apply(this, arguments));
- };
- };
-
- this._convertScale = function (f) {
- f = geo.util.ensureFunction(f);
- return function () {
- return f.apply(this, arguments) / m_scale;
- };
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Set styles to a d3 selection. Ignores unkown style keys.
- * @private
+ * Build
+ *
+ * @override
*/
////////////////////////////////////////////////////////////////////////////
- function setStyles(select, styles) {
- /* jshint validthis:true */
- var key, k, f;
- function fillFunc() {
- if (styles.fill.apply(this, arguments)) {
- return null;
- } else {
- return 'none';
- }
- }
- function strokeFunc() {
- if (styles.stroke.apply(this, arguments)) {
- return null;
- } else {
- return 'none';
- }
- }
- for (key in styles) {
- if (styles.hasOwnProperty(key)) {
- f = null;
- k = null;
- if (key === 'strokeColor') {
- k = 'stroke';
- f = m_this._convertColor(styles[key], styles.stroke);
- } else if (key === 'stroke' && styles[key]) {
- k = 'stroke';
- f = strokeFunc;
- } else if (key === 'strokeWidth') {
- k = 'stroke-width';
- f = m_this._convertScale(styles[key]);
- } else if (key === 'strokeOpacity') {
- k = 'stroke-opacity';
- f = styles[key];
- } else if (key === 'fillColor') {
- k = 'fill';
- f = m_this._convertColor(styles[key], styles.fill);
- } else if (key === 'fill' && !styles.hasOwnProperty('fillColor')) {
- k = 'fill';
- f = fillFunc;
- } else if (key === 'fillOpacity') {
- k = 'fill-opacity';
- f = styles[key];
- }
- if (k) {
- select.style(k, f);
- }
- }
- }
- }
+ this._build = function () {
+ var data = m_this.data() || [],
+ s_style = m_this.style(),
+ tmp, diag;
+ s_update.call(m_this);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get the map instance or return null if not connected to a map.
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- function getMap() {
- var layer = m_this.layer();
- if (!layer) {
- return null;
- }
- return layer.map();
- }
+ diag = function (d) {
+ var p = {
+ source: d.source,
+ target: d.target
+ };
+ return d3.svg.diagonal()(p);
+ };
+ tmp = [];
+ data.forEach(function (d, i) {
+ var src, trg;
+ if (i < data.length - 1) {
+ src = d;
+ trg = data[i + 1];
+ tmp.push({
+ source: m_this.featureGcsToDisplay(src),
+ target: m_this.featureGcsToDisplay(trg)
+ });
+ }
+ });
+ m_style.data = tmp;
+ m_style.attributes = {
+ d: diag
+ };
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Get the svg group element associated with this renderer instance.
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- function getGroup() {
- return m_svg.select('.group-' + m_this._d3id());
- }
+ m_style.id = m_this._d3id();
+ m_style.append = 'path';
+ m_style.classes = ['d3PathFeature'];
+ m_style.style = $.extend({
+ 'fill': function () { return false; },
+ 'fillColor': function () { return { r: 0, g: 0, b: 0 }; }
+ }, s_style);
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Set the initial lat-lon coordinates of the map view.
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- function initCorners() {
- var layer = m_this.layer(),
- map = layer.map(),
- width = m_this.layer().width(),
- height = m_this.layer().height();
+ m_this.renderer()._drawFeatures(m_style);
- m_width = width;
- m_height = height;
- if (!m_width || !m_height) {
- throw 'Map layer has size 0';
- }
- m_corners = {
- 'upperLeft': map.displayToGcs({'x': 0, 'y': 0}),
- 'lowerRight': map.displayToGcs({'x': width, 'y': height})
- };
- }
+ m_buildTime.modified();
+ m_this.updateTime().modified();
+ return m_this;
+ };
////////////////////////////////////////////////////////////////////////////
/**
- * Set the translation, scale, and zoom for the current view.
- * @note rotation not yet supported
- * @private
+ * Update
+ *
+ * @override
*/
////////////////////////////////////////////////////////////////////////////
- function setTransform() {
-
- if (!m_corners) {
- initCorners();
- }
+ this._update = function () {
+ s_update.call(m_this);
- if (!m_sticky) {
- return;
+ if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) {
+ m_this._build();
}
- var layer = m_this.layer(),
- map = layer.map(),
- upperLeft = map.gcsToDisplay(m_corners.upperLeft),
- lowerRight = map.gcsToDisplay(m_corners.lowerRight),
- group = getGroup(),
- dx, dy, scale;
+ return m_this;
+ };
- // calculate the translation
- dx = upperLeft.x;
- dy = upperLeft.y;
+ this._init(arg);
+ return this;
+};
- // calculate the scale
- scale = (lowerRight.y - upperLeft.y) / m_height;
+inherit(geo.d3.pathFeature, geo.pathFeature);
- // set the group transform property
- group.attr('transform', 'matrix(' + [scale, 0, 0, scale, dx, dy].join() + ')');
+geo.registerFeature('d3', 'path', geo.d3.pathFeature);
- // set internal variables
- m_scale = scale;
- m_dx = dx;
- m_dy = dy;
- }
+/**
+ * @class
+ * @extends geo.graphFeature
+ */
+geo.d3.graphFeature = function (arg) {
+ 'use strict';
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert from screen pixel coordinates to the local coordinate system
- * in the SVG group element taking into account the transform.
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- function baseToLocal(pt) {
- return {
- x: (pt.x - m_dx) / m_scale,
- y: (pt.y - m_dy) / m_scale
- };
- }
+ var m_this = this;
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Convert from the local coordinate system in the SVG group element
- * to screen pixel coordinates.
- * @private
- */
- ////////////////////////////////////////////////////////////////////////////
- function localToBase(pt) {
- return {
- x: pt.x * m_scale + m_dx,
- y: pt.y * m_scale + m_dy
- };
+ if (!(this instanceof geo.d3.graphFeature)) {
+ return new geo.d3.graphFeature(arg);
}
+ geo.graphFeature.call(this, arg);
////////////////////////////////////////////////////////////////////////////
/**
- * Initialize
- */
+ * Returns a d3 selection for the graph elements
+ */
////////////////////////////////////////////////////////////////////////////
- this._init = function () {
- if (!m_this.canvas()) {
- var canvas;
- m_svg = d3.select(m_this.layer().node().get(0)).append('svg');
-
- // create a global svg definitions element
- m_defs = m_svg.append('defs');
-
- var shadow = m_defs
- .append('filter')
- .attr('id', 'geo-highlight')
- .attr('x', '-100%')
- .attr('y', '-100%')
- .attr('width', '300%')
- .attr('height', '300%');
- shadow
- .append('feMorphology')
- .attr('operator', 'dilate')
- .attr('radius', 2)
- .attr('in', 'SourceAlpha')
- .attr('result', 'dilateOut');
- shadow
- .append('feGaussianBlur')
- .attr('stdDeviation', 5)
- .attr('in', 'dilateOut')
- .attr('result', 'blurOut');
- shadow
- .append('feColorMatrix')
- .attr('type', 'matrix')
- .attr('values', '-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0')
- .attr('in', 'blurOut')
- .attr('result', 'invertOut');
- shadow
- .append('feBlend')
- .attr('in', 'SourceGraphic')
- .attr('in2', 'invertOut')
- .attr('mode', 'normal');
- canvas = m_svg.append('g');
+ this.select = function () {
+ var renderer = m_this.renderer(),
+ selection = {},
+ node = m_this.nodeFeature(),
+ links = m_this.linkFeatures();
+ selection.nodes = renderer.select(node._d3id());
+ selection.links = links.map(function (link) {
+ return renderer.select(link._d3id());
+ });
+ return selection;
+ };
- shadow = m_defs.append('filter')
- .attr('id', 'geo-blur')
- .attr('x', '-100%')
- .attr('y', '-100%')
- .attr('width', '300%')
- .attr('height', '300%');
+ return this;
+};
- shadow
- .append('feGaussianBlur')
- .attr('stdDeviation', 20)
- .attr('in', 'SourceGraphic');
+inherit(geo.d3.graphFeature, geo.graphFeature);
- m_sticky = m_this.layer().sticky();
- m_svg.attr('class', m_this._d3id());
- m_svg.attr('width', m_this.layer().node().width());
- m_svg.attr('height', m_this.layer().node().height());
+geo.registerFeature('d3', 'graph', geo.d3.graphFeature);
- canvas.attr('class', 'group-' + m_this._d3id());
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a plane feature given a lower left corner point
+ * and and upper right corner point
+ *
+ * @class
+ * @extends geo.planeFeature
+ * @param lowerleft
+ * @param upperright
+ * @returns {geo.d3.planeFeature}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.d3.planeFeature = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.d3.planeFeature)) {
+ return new geo.d3.planeFeature(arg);
+ }
+ geo.planeFeature.call(this, arg);
+ geo.d3.object.call(this);
- m_this.canvas(canvas);
- }
- };
+ var m_this = this,
+ m_style = {},
+ s_update = this._update,
+ s_init = this._init,
+ m_buildTime = geo.timestamp();
- ////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
/**
- * Convert from coordinates in the svg group element to lat/lon.
- * Supports objects or arrays of objects.
+ * Normalize a coordinate as an object {x: ..., y: ...}
+ *
+ * @private
+ * @returns {Object}
*/
- ////////////////////////////////////////////////////////////////////////////
- this.displayToWorld = function (pt) {
- var map = getMap();
- if (!map) {
- throw 'Cannot project until this layer is connected to a map.';
- }
+ //////////////////////////////////////////////////////////////////////////////
+ function normalize(pt) {
if (Array.isArray(pt)) {
- pt = pt.map(function (x) {
- return map.displayToGcs(localToBase(x));
- });
- } else {
- pt = map.displayToGcs(localToBase(pt));
+ return {
+ x: pt[0],
+ y: pt[1]
+ };
}
return pt;
- };
+ }
- ////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
/**
- * Convert from lat/lon to pixel coordinates in the svg group element.
- * Supports objects or arrays of objects.
+ * Build the feature object and pass to the renderer for drawing.
+ *
+ * @private
+ * @returns {geo.d3.planeFeature}
*/
- ////////////////////////////////////////////////////////////////////////////
- this.worldToDisplay = function (pt) {
- var map = getMap();
- if (!map) {
- throw 'Cannot project until this layer is connected to a map.';
+ //////////////////////////////////////////////////////////////////////////////
+ this._build = function () {
+ var ul = normalize(m_this.upperLeft()),
+ lr = normalize(m_this.lowerRight()),
+ renderer = m_this.renderer(),
+ s = m_this.style();
+
+ delete s.fill_color;
+ delete s.color;
+ delete s.opacity;
+ /*
+ if (!s.screenCoordinates) {
+ origin = renderer.layer().map().worldToDisplay(origin);
+ ul = renderer.layer().map().worldToDisplay(ul);
+ lr = renderer.layer().map().worldToDisplay(lr);
}
- var v;
- if (Array.isArray(pt)) {
- v = pt.map(function (x) {
- return baseToLocal(map.gcsToDisplay(x));
- });
+ */
+ m_style.id = m_this._d3id();
+ m_style.style = s;
+ m_style.attributes = {
+ x: ul.x,
+ y: ul.y,
+ width: Math.abs(lr.x - ul.x),
+ height: Math.abs(lr.y - ul.y),
+ reference: s.reference
+ };
+ if (s.image) {
+ m_style.append = 'image';
+ m_style.attributes['xlink:href'] = s.image;
} else {
- v = baseToLocal(map.gcsToDisplay(pt));
+ m_style.append = 'rect';
}
- return v;
+ m_style.data = [0];
+ m_style.classes = ['d3PlaneFeature'];
+ if (s.parentId) {
+ m_style.parentId = s.parentId;
+ }
+
+ renderer._drawFeatures(m_style);
+ m_buildTime.modified();
+ return m_this;
};
- ////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
/**
- * Get API used by the renderer
+ * Redraw the plane feature if necessary.
+ *
+ * @private
+ * @returns {geo.d3.planeFeature}
*/
- ////////////////////////////////////////////////////////////////////////////
- this.api = function () {
- return 'd3';
+ //////////////////////////////////////////////////////////////////////////////
+ this._update = function () {
+ s_update.call(m_this);
+
+ if (m_this.dataTime().getMTime() >= m_buildTime.getMTime()) {
+ m_this._build();
+ }
+ return m_this;
};
- ////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
/**
- * Return the current scaling factor to build features that shouldn't
- * change size during zooms. For example:
- *
- * selection.append('circle')
- * .attr('r', r0 / renderer.scaleFactor());
+ * Initializes the plane feature style (over-riding the parent default).
*
- * This will create a circle element with radius r0 independent of the
- * current zoom level.
+ * @private
+ * @returns {geo.d3.planeFeature}
*/
- ////////////////////////////////////////////////////////////////////////////
- this.scaleFactor = function () {
- return m_scale;
+ //////////////////////////////////////////////////////////////////////////////
+ this._init = function (arg) {
+ s_init.call(m_this, arg || {});
+ m_this.style({
+ stroke: function () { return false; },
+ fill: function () { return true; },
+ fillColor: function () { return {r: 0.3, g: 0.3, b: 0.3}; },
+ fillOpacity: function () { return 0.5; }
+ });
+ return m_this;
};
+ this._init();
+ return this;
+};
+
+inherit(geo.d3.planeFeature, geo.planeFeature);
+
+geo.registerFeature('d3', 'plane', geo.d3.planeFeature);
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of vectorFeature
+ *
+ * @class
+ * @extends geo.vectorFeature
+ * @extends geo.d3.object
+ * @returns {geo.d3.vectorFeature}
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.d3.vectorFeature = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.d3.vectorFeature)) {
+ return new geo.d3.vectorFeature(arg);
+ }
+ arg = arg || {};
+ geo.vectorFeature.call(this, arg);
+ geo.d3.object.call(this);
+
////////////////////////////////////////////////////////////////////////////
/**
- * Handle resize event
+ * @private
*/
////////////////////////////////////////////////////////////////////////////
- this._resize = function (x, y, w, h) {
- if (!m_corners) {
- initCorners();
- }
- m_svg.attr('width', w);
- m_svg.attr('height', h);
- setTransform();
- m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true);
- };
+ var m_this = this,
+ s_init = this._init,
+ s_exit = this._exit,
+ s_update = this._update,
+ m_buildTime = geo.timestamp(),
+ m_style = {},
+ m_sticky;
////////////////////////////////////////////////////////////////////////////
/**
- * Update noop for geo.d3.object api.
+ * Generate a unique ID for a marker definition
+ * @private
+ * @param {object} d Unused datum (for d3 compat)
+ * @param {number} i The marker index
*/
////////////////////////////////////////////////////////////////////////////
- this._update = function () {
- };
+ function markerID(d, i) {
+ return m_this._d3id() + '_marker_' + i;
+ }
////////////////////////////////////////////////////////////////////////////
/**
- * Exit
+ * Add marker styles for vector arrows.
+ * @private
+ * @param {object[]} data The vector data array
+ * @param {function} stroke The stroke accessor
+ * @param {function} opacity The opacity accessor
*/
////////////////////////////////////////////////////////////////////////////
- this._exit = function () {
- m_features = {};
- m_this.canvas().remove();
- s_exit();
- };
+ function updateMarkers(data, stroke, opacity) {
+
+ var renderer = m_this.renderer();
+ var sel = m_this.renderer()._definitions()
+ .selectAll('marker.geo-vector')
+ .data(data);
+ sel.enter()
+ .append('marker')
+ .attr('id', markerID)
+ .attr('class', 'geo-vector')
+ .attr('viewBox', '0 0 10 10')
+ .attr('refX', '1')
+ .attr('refY', '5')
+ .attr('markerWidth', '5')
+ .attr('markerHeight', '5')
+ .attr('orient', 'auto')
+ .append('path')
+ .attr('d', 'M 0 0 L 10 5 L 0 10 z');
+
+ sel.exit().remove();
+
+ sel.style('stroke', renderer._convertColor(stroke))
+ .style('fill', renderer._convertColor(stroke))
+ .style('opacity', opacity);
+ }
////////////////////////////////////////////////////////////////////////////
/**
- * Get the definitions dom element for the layer
+ * Initialize
* @protected
*/
////////////////////////////////////////////////////////////////////////////
- this._definitions = function () {
- return m_defs;
+ this._init = function (arg) {
+ s_init.call(m_this, arg);
+ m_sticky = m_this.layer().sticky();
+ return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Create a new feature element from an object that describes the feature
- * attributes. To be called from feature classes only.
- *
- * Input:
- * {
- * id: A unique string identifying the feature.
- * data: Array of data objects used in a d3 data method.
- * index: A function that returns a unique id for each data element.
- * style: An object containing element CSS styles.
- * attributes: An object containing element attributes.
- * classes: An array of classes to add to the elements.
- * append: The element type as used in d3 append methods.
- * }
+ * Build
+ * @protected
*/
////////////////////////////////////////////////////////////////////////////
- this._drawFeatures = function (arg) {
- m_features[arg.id] = {
- data: arg.data,
- index: arg.dataIndex,
- style: arg.style,
- attributes: arg.attributes,
- classes: arg.classes,
- append: arg.append
- };
- return m_this.__render(arg.id);
- };
+ this._build = function () {
+ var data = m_this.data(),
+ s_style = m_this.style.get(),
+ m_renderer = m_this.renderer(),
+ orig_func = m_this.origin(),
+ size_func = m_this.delta(),
+ cache = [],
+ scale = m_this.style('scale'),
+ max = Number.NEGATIVE_INFINITY;
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Updates a feature by performing a d3 data join. If no input id is
- * provided then this method will update all features.
- */
- ////////////////////////////////////////////////////////////////////////////
- this.__render = function (id) {
- var key;
- if (id === undefined) {
- for (key in m_features) {
- if (m_features.hasOwnProperty(key)) {
- m_this.__render(key);
- }
- }
- return m_this;
+ // call super-method
+ s_update.call(m_this);
+
+ // default to empty data array
+ if (!data) { data = []; }
+
+ // cache the georeferencing
+ cache = data.map(function (d, i) {
+ var origin = m_this.featureGcsToDisplay(orig_func(d, i)),
+ delta = size_func(d, i);
+ max = Math.max(max, delta.x * delta.x + delta.y * delta.y);
+ return {
+ x1: origin.x,
+ y1: origin.y,
+ dx: delta.x,
+ dy: -delta.y
+ };
+ });
+
+ max = Math.sqrt(max);
+ if (!scale) {
+ scale = 75 / max;
}
- var data = m_features[id].data,
- index = m_features[id].index,
- style = m_features[id].style,
- attributes = m_features[id].attributes,
- classes = m_features[id].classes,
- append = m_features[id].append,
- selection = m_this.select(id).data(data, index);
- selection.enter().append(append);
- selection.exit().remove();
- setAttrs(selection, attributes);
- selection.attr('class', classes.concat([id]).join(' '));
- setStyles(selection, style);
+
+ function getScale() {
+ return scale / m_renderer.scaleFactor();
+ }
+
+ // fill in d3 renderer style object defaults
+ m_style.id = m_this._d3id();
+ m_style.data = data;
+ m_style.append = 'line';
+ m_style.attributes = {
+ x1: function (d, i) {
+ return cache[i].x1;
+ },
+ y1: function (d, i) {
+ return cache[i].y1;
+ },
+ x2: function (d, i) {
+ return cache[i].x1 + getScale() * cache[i].dx;
+ },
+ y2: function (d, i) {
+ return cache[i].y1 + getScale() * cache[i].dy;
+ },
+ 'marker-end': function (d, i) {
+ return 'url(#' + markerID(d, i) + ')';
+ }
+ };
+ m_style.style = {
+ stroke: function () { return true; },
+ strokeColor: s_style.strokeColor,
+ strokeWidth: s_style.strokeWidth,
+ strokeOpacity: s_style.strokeOpacity
+ };
+ m_style.classes = ['d3VectorFeature'];
+
+ // Add markers to the defition list
+ updateMarkers(data, s_style.strokeColor, s_style.strokeOpacity);
+
+ // pass to renderer to draw
+ m_this.renderer()._drawFeatures(m_style);
+
+ // update time stamps
+ m_buildTime.modified();
+ m_this.updateTime().modified();
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Returns a d3 selection for the given feature id.
- */
+ * Update
+ * @protected
+ */
////////////////////////////////////////////////////////////////////////////
- this.select = function (id) {
- return getGroup().selectAll('.' + id);
- };
+ this._update = function () {
+ s_update.call(m_this);
+
+ if (m_this.getMTime() >= m_buildTime.getMTime()) {
+ m_this._build();
+ } else {
+ updateMarkers(
+ m_style.data,
+ m_style.style.strokeColor,
+ m_style.style.strokeOpacity
+ );
+ }
- ////////////////////////////////////////////////////////////////////////////
- /**
- * Removes a feature from the layer.
- */
- ////////////////////////////////////////////////////////////////////////////
- this._removeFeature = function (id) {
- m_this.select(id).remove();
- delete m_features[id];
return m_this;
};
////////////////////////////////////////////////////////////////////////////
/**
- * Override draw method to do nothing.
- */
+ * Exit
+ * @protected
+ */
////////////////////////////////////////////////////////////////////////////
- this.draw = function () {
+ this._exit = function () {
+ s_exit.call(m_this);
+ m_style = {};
+ updateMarkers([], null, null);
};
- // connect to pan event
- this.layer().geoOn(geo.event.pan, setTransform);
-
- // connect to zoom event
- this.layer().geoOn(geo.event.zoom, function () {
- setTransform();
- m_this.__render();
- m_this.layer().geoTrigger(geo.event.d3Rescale, { scale: m_scale }, true);
- });
-
- this.layer().geoOn(geo.event.resize, function (event) {
- m_this._resize(event.x, event.y, event.width, event.height);
- });
-
this._init(arg);
return this;
};
-inherit(geo.d3.d3Renderer, geo.renderer);
+inherit(geo.d3.vectorFeature, geo.vectorFeature);
-geo.registerRenderer('d3', geo.d3.d3Renderer);
+// Now register it
+geo.registerFeature('d3', 'vector', geo.d3.vectorFeature);
//////////////////////////////////////////////////////////////////////////////
/**
@@ -26277,8 +28368,8 @@ geo.gui = {};
geo.gui.uiLayer = function (arg) {
'use strict';
- // The widget stays fixed on the screen. (only available in d3 at the moment)
- arg.renderer = 'd3';
+ // The widget stays fixed on the screen.
+ arg.renderer = 'dom';
arg.sticky = false;
if (!(this instanceof geo.gui.uiLayer)) {
@@ -26297,12 +28388,14 @@ geo.gui.uiLayer = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
this.createWidget = function (widgetName, arg) {
+ var newWidget = geo.createWidget(widgetName, m_this, arg);
- var newWidget = geo.createWidget(
- widgetName, m_this, m_this.renderer(), arg);
+ // We only want top level widgets to be a child of the uiLayer
+ if (!(arg && 'parent' in arg)) {
+ m_this.addChild(newWidget);
+ }
- m_this.addChild(newWidget);
- newWidget._init();
+ newWidget._init(arg);
m_this.modified();
return newWidget;
};
@@ -26354,7 +28447,14 @@ geo.gui.widget = function (arg) {
var m_this = this,
s_exit = this._exit,
- m_layer = arg.layer;
+ m_layer = arg.layer,
+ m_canvas = null;
+
+ arg.position = arg.position === undefined ? { left: 0, top: 0 } : arg.position;
+
+ if (arg.parent !== undefined && !(arg.parent instanceof geo.gui.widget)) {
+ throw 'Parent must be of type geo.gui.widget';
+ }
this._init = function () {
m_this.modified();
@@ -26364,6 +28464,9 @@ geo.gui.widget = function (arg) {
m_this.children().forEach(function (child) {
m_this._deleteFeature(child);
});
+
+ m_this.layer().geoOff(geo.event.pan, m_this.repositionEvent);
+ m_this.parentCanvas().removeChild(m_this.canvas());
s_exit();
};
@@ -26403,15 +28506,255 @@ geo.gui.widget = function (arg) {
this.layer = function () {
return m_layer;
};
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Create the canvas this widget will operate on.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._createCanvas = function () {
+ throw 'Must be defined in derived classes';
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get/Set the canvas for the widget
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.canvas = function (val) {
+ if (val === undefined) {
+ return m_canvas;
+ } else {
+ m_canvas = val;
+ }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Appends a child to the widget
+ * The widget determines how to append itself to a parent, the parent can either
+ * be another widget, or the UI Layer.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._appendChild = function () {
+ m_this.parentCanvas().appendChild(m_this.canvas());
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Get the parent canvas (top level widgets define their layer as their parent canvas)
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.parentCanvas = function () {
+ if (m_this.parent === undefined) {
+ return m_this.layer().canvas();
+ } else {
+ return m_this.parent().canvas();
+ }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Gets the CSS positioning that a widget should be placed at.
+ * { top: 0, left: 0 } by default.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.position = function () {
+ var position;
+
+ if (arg &&
+ arg.hasOwnProperty('position') &&
+ arg.position.hasOwnProperty('x') &&
+ arg.position.hasOwnProperty('y')) {
+
+ position = m_this.layer().map().gcsToDisplay(arg.position);
+
+ return {
+ left: position.x,
+ top: position.y
+ };
+ }
+
+ return arg.position;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Repositions a widget based on the argument passed, or calling position on
+ * the widget itself.
+ * @param {object} position A position with the form:
+ * { top: m, left: n }
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.reposition = function (position) {
+ position = position || m_this.position();
+ m_this.canvas().style.position = 'absolute';
+
+ for (var cssAttr in position) {
+ if (position.hasOwnProperty(cssAttr)) {
+ m_this.canvas().style[cssAttr] = position[cssAttr] + 'px';
+ }
+ }
+ };
+
+ this.repositionEvent = function () {
+ return m_this.reposition();
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Determines whether or not the widget is completely within the viewport.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this.isInViewport = function () {
+ var position = m_this.position();
+ var layer = m_this.layer();
+
+ return ((position.left >= 0 && position.top >= 0) &&
+ (position.left <= layer.width() && position.top <= layer.height()));
+ };
+
+ if (arg &&
+ arg.hasOwnProperty('position') &&
+ arg.position.hasOwnProperty('x') &&
+ arg.position.hasOwnProperty('y')) {
+ this.layer().geoOn(geo.event.pan, m_this.repositionEvent);
+ }
};
inherit(geo.gui.widget, geo.sceneObject);
+geo.gui.domWidget = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.gui.domWidget)) {
+ return new geo.gui.domWidget(arg);
+ }
+
+ geo.gui.widget.call(this, arg);
+
+ var m_this = this,
+ m_default_canvas = 'div';
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initializes DOM Widget.
+ * Sets the canvas for the widget, does parent/child relationship management,
+ * appends it to it's parent and handles any positioning logic.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._init = function () {
+ if (arg.hasOwnProperty('parent')) {
+ arg.parent.addChild(m_this);
+ }
+
+ m_this._createCanvas();
+ m_this._appendChild();
+
+ m_this.canvas().addEventListener('mousedown', function (e) {
+ e.stopPropagation();
+ });
+
+ m_this.reposition();
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Creates the widget canvas.
+ * This is just a simple DOM element (based on args.el, or defaults to a div)
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._createCanvas = function () {
+ m_this.canvas(document.createElement(arg.el || m_default_canvas));
+ };
+
+ return this;
+};
+
+inherit(geo.gui.domWidget, geo.gui.widget);
+
+geo.registerWidget('dom', 'dom', geo.gui.domWidget);
+
+//////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new instance of class geo.gui.svgWidget
+ *
+ * Due to the nature of d3 creating DOM elements as it inserts them, calls to appendChild
+ * don't appear in this widget.
+ *
+ * The canvas of an svgWidget always refers to the actual svg element.
+ * The parentCanvas can refer to another widgets svg element, dom element, or the
+ * UI layers dom element.
+ * See {@link geo.gui.widget#parentCanvas}.
+ *
+ * @class
+ * @extends geo.gui.domWidget
+ * @returns {geo.gui.svgWidget}
+ *
+ */
+//////////////////////////////////////////////////////////////////////////////
+geo.gui.svgWidget = function (arg) {
+ 'use strict';
+ if (!(this instanceof geo.gui.svgWidget)) {
+ return new geo.gui.svgWidget(arg);
+ }
+
+ geo.gui.domWidget.call(this, arg);
+
+ var m_this = this,
+ m_renderer = null;
+
+ this._init = function (arg) {
+ var d3Parent;
+ if (arg.hasOwnProperty('parent')) {
+ arg.parent.addChild(m_this);
+
+ // Tell the renderer there is an SVG element as a parent
+ d3Parent = arg.parent.canvas();
+ }
+
+ m_this._createCanvas(d3Parent);
+
+ m_this.canvas().addEventListener('mousedown', function (e) {
+ e.stopPropagation();
+ });
+
+ m_this.reposition();
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Creates the canvas for the svg widget.
+ * This directly uses the {@link geo.d3.d3Renderer} as a helper to do all of the heavy
+ * lifting.
+ */
+ ////////////////////////////////////////////////////////////////////////////
+ this._createCanvas = function (d3Parent) {
+ var rendererOpts = {
+ layer: m_this.layer(),
+ widget: true
+ };
+
+ if (d3Parent) {
+ rendererOpts.d3Parent = d3Parent;
+ }
+
+ m_renderer = geo.d3.d3Renderer(rendererOpts);
+
+ m_this.canvas(m_renderer.canvas()[0][0]);
+ };
+
+ return this;
+};
+
+inherit(geo.gui.svgWidget, geo.gui.domWidget);
+
+geo.registerWidget('dom', 'svg', geo.gui.svgWidget);
+
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of class sliderWidget
*
* @class
- * @extends {geo.gui.widget}
+ * @extends {geo.gui.svgWidget}
* @returns {geo.gui.sliderWidget}
*/
//////////////////////////////////////////////////////////////////////////////
@@ -26420,10 +28763,12 @@ geo.gui.sliderWidget = function (arg) {
if (!(this instanceof geo.gui.sliderWidget)) {
return new geo.gui.sliderWidget(arg);
}
- geo.gui.widget.call(this, arg);
+ geo.gui.svgWidget.call(this, arg);
var m_this = this,
s_exit = this._exit,
+ s_createCanvas = this._createCanvas,
+ s_appendChild = this._appendChild,
m_xscale,
m_yscale,
m_plus,
@@ -26452,20 +28797,20 @@ geo.gui.sliderWidget = function (arg) {
black: '#505050'
};
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Add an icon from a path string. Returns a d3 group element.
- *
- * @function
- * @argument {String} icon svg path string
- * @argument {Array} base where to append the element (d3 selection)
- * @argument {Number} cx Center x-coordinate
- * @argument {Number} cy Center y-coordinate
- * @argument {Number} size Icon size in pixels
- * @returns {object}
- * @private
- */
-//////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Add an icon from a path string. Returns a d3 group element.
+ *
+ * @function
+ * @argument {String} icon svg path string
+ * @argument {Array} base where to append the element (d3 selection)
+ * @argument {Number} cx Center x-coordinate
+ * @argument {Number} cy Center y-coordinate
+ * @argument {Number} size Icon size in pixels
+ * @returns {object}
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
function put_icon(icon, base, cx, cy, size) {
var g = base.append('g');
@@ -26485,17 +28830,22 @@ geo.gui.sliderWidget = function (arg) {
return g;
}
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Initialize the slider widget in the map.
- *
- * @function
- * @returns {geo.gui.sliderWidget}
- * @private
- */
-//////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Initialize the slider widget in the map.
+ *
+ * @function
+ * @returns {geo.gui.sliderWidget}
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
this._init = function () {
- var svg = m_this.layer().renderer().canvas(),
+ s_createCanvas();
+ s_appendChild();
+
+ m_this.reposition();
+
+ var svg = d3.select(m_this.canvas()),
x0 = 40,
y0 = 40 + m_width,
map = m_this.layer().map();
@@ -26693,31 +29043,31 @@ geo.gui.sliderWidget = function (arg) {
m_this._update();
};
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Removes the slider element from the map and unbinds all handlers.
- *
- * @function
- * @returns {geo.gui.sliderWidget}
- * @private
- */
-//////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Removes the slider element from the map and unbinds all handlers.
+ *
+ * @function
+ * @returns {geo.gui.sliderWidget}
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
this._exit = function () {
m_group.remove();
m_this.layer().geoOff(geo.event.zoom);
s_exit();
};
-//////////////////////////////////////////////////////////////////////////////
-/**
- * Update the slider widget state in reponse to map changes. I.e. zoom
- * range changes.
- *
- * @function
- * @returns {geo.gui.sliderWidget}
- * @private
- */
-//////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////
+ /**
+ * Update the slider widget state in reponse to map changes. I.e. zoom
+ * range changes.
+ *
+ * @function
+ * @returns {geo.gui.sliderWidget}
+ * @private
+ */
+ //////////////////////////////////////////////////////////////////////////////
this._update = function (obj) {
var map = m_this.layer().map(),
zoomRange = map.zoomRange(),
@@ -26734,16 +29084,16 @@ geo.gui.sliderWidget = function (arg) {
};
};
-inherit(geo.gui.sliderWidget, geo.gui.widget);
+inherit(geo.gui.sliderWidget, geo.gui.svgWidget);
-geo.registerWidget('d3', 'slider', geo.gui.sliderWidget);
+geo.registerWidget('dom', 'slider', geo.gui.sliderWidget);
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of class legendWidget
*
* @class
- * @extends geo.gui.widget
+ * @extends geo.gui.svgWidget
* @returns {geo.gui.legendWidget}
*/
//////////////////////////////////////////////////////////////////////////////
@@ -26752,7 +29102,7 @@ geo.gui.legendWidget = function (arg) {
if (!(this instanceof geo.gui.legendWidget)) {
return new geo.gui.legendWidget(arg);
}
- geo.gui.widget.call(this, arg);
+ geo.gui.svgWidget.call(this, arg);
/** @private */
var m_this = this,
@@ -26761,7 +29111,9 @@ geo.gui.legendWidget = function (arg) {
m_group = null,
m_border = null,
m_spacing = 20, // distance in pixels between lines
- m_padding = 12; // padding in pixels inside the border
+ m_padding = 12, // padding in pixels inside the border
+ s_createCanvas = this._createCanvas,
+ s_appendChild = this._appendChild;
//////////////////////////////////////////////////////////////////////////////
/**
@@ -26813,8 +29165,8 @@ geo.gui.legendWidget = function (arg) {
//////////////////////////////////////////////////////////////////////////////
this.size = function () {
var width = 1, height;
- var test = m_this.layer().renderer().canvas().append('text')
- .style('opacity', 1e-6);
+ var test = d3.select(m_this.canvas()).append('text')
+ .style('opacity', 1e-6);
m_categories.forEach(function (d) {
test.text(d.name);
@@ -26839,12 +29191,12 @@ geo.gui.legendWidget = function (arg) {
m_this._init();
function applyColor(selection) {
selection.style('fill', function (d) {
- if (d.style.fill || d.style.fill === undefined) {
- return d.style.fillColor;
- } else {
- return 'none';
- }
- })
+ if (d.style.fill || d.style.fill === undefined) {
+ return d.style.fillColor;
+ } else {
+ return 'none';
+ }
+ })
.style('fill-opacity', function (d) {
if (d.style.fillOpacity === undefined) {
return 1;
@@ -26878,41 +29230,41 @@ geo.gui.legendWidget = function (arg) {
var scale = m_this._scale();
var labels = m_group.selectAll('g.geo-label')
- .data(m_categories, function (d) { return d.name; });
+ .data(m_categories, function (d) { return d.name; });
var g = labels.enter().append('g')
- .attr('class', 'geo-label')
- .attr('transform', function (d, i) {
- return 'translate(0,' + scale.y(i) + ')';
- });
+ .attr('class', 'geo-label')
+ .attr('transform', function (d, i) {
+ return 'translate(0,' + scale.y(i) + ')';
+ });
applyColor(g.filter(function (d) {
- return d.type !== 'point' && d.type !== 'line';
- }).append('rect')
- .attr('x', 0)
- .attr('y', -6)
- .attr('rx', 5)
- .attr('ry', 5)
- .attr('width', 40)
- .attr('height', 12)
- );
+ return d.type !== 'point' && d.type !== 'line';
+ }).append('rect')
+ .attr('x', 0)
+ .attr('y', -6)
+ .attr('rx', 5)
+ .attr('ry', 5)
+ .attr('width', 40)
+ .attr('height', 12)
+ );
applyColor(g.filter(function (d) {
- return d.type === 'point';
- }).append('circle')
- .attr('cx', 20)
- .attr('cy', 0)
- .attr('r', 6)
- );
+ return d.type === 'point';
+ }).append('circle')
+ .attr('cx', 20)
+ .attr('cy', 0)
+ .attr('r', 6)
+ );
applyColor(g.filter(function (d) {
- return d.type === 'line';
- }).append('line')
- .attr('x1', 0)
- .attr('y1', 0)
- .attr('x2', 40)
- .attr('y2', 0)
- );
+ return d.type === 'line';
+ }).append('line')
+ .attr('x1', 0)
+ .attr('y1', 0)
+ .attr('x2', 40)
+ .attr('y2', 0)
+ );
g.append('text')
.attr('x', '50px')
@@ -26922,6 +29274,8 @@ geo.gui.legendWidget = function (arg) {
return d.name;
});
+ m_this.reposition();
+
return m_this;
};
@@ -26950,23 +29304,33 @@ geo.gui.legendWidget = function (arg) {
*/
//////////////////////////////////////////////////////////////////////////////
this._init = function () {
- var w = m_this.size().width + 2 * m_padding,
- h = m_this.size().height + 2 * m_padding,
- nw = m_this.layer().map().node().width(),
- margin = 20;
+ // adding categories redraws the entire thing by calling _init, see
+ // the m_top.remove() line below
+ if (!m_top) {
+ s_createCanvas();
+ s_appendChild();
+ }
+
+ // total size = interior size + 2 * padding + 2 * width of the border
+ var w = m_this.size().width + 2 * m_padding + 4,
+ h = m_this.size().height + 2 * m_padding + 4;
+
+ // @todo - removing after creating to maintain the appendChild structure
if (m_top) {
m_top.remove();
}
- m_top = m_this.layer().renderer().canvas().append('g')
- .attr('transform', 'translate(' + (nw - w - margin) + ',' + margin + ')');
+
+ d3.select(m_this.canvas()).attr('width', w).attr('height', h);
+
+ m_top = d3.select(m_this.canvas()).append('g');
m_group = m_top
.append('g')
- .attr('transform', 'translate(' + [m_padding - 1.5, m_padding] + ')');
+ .attr('transform', 'translate(' + [m_padding + 2, m_padding + 2] + ')');
m_border = m_group.append('rect')
.attr('x', -m_padding)
.attr('y', -m_padding)
- .attr('width', w)
- .attr('height', h)
+ .attr('width', w - 4)
+ .attr('height', h - 4)
.attr('rx', 3)
.attr('ry', 3)
.style({
@@ -26989,17 +29353,19 @@ geo.gui.legendWidget = function (arg) {
.duration(250)
.style('fill-opacity', 0.75);
});
+
+ m_this.reposition();
};
this.geoOn(geo.event.resize, function () {
- this.draw();
+ m_this.draw();
});
};
-inherit(geo.gui.legendWidget, geo.gui.widget);
+inherit(geo.gui.legendWidget, geo.gui.svgWidget);
-geo.registerWidget('d3', 'legend', geo.gui.legendWidget);
+geo.registerWidget('dom', 'legend', geo.gui.legendWidget);
/*jscs:disable validateIndentation*/
(function ($, geo, d3) {
@@ -27259,7 +29625,7 @@ geo.registerWidget('d3', 'legend', geo.gui.legendWidget);
* Describes layers added to the map
* @property {boolean} [autoresize=true]
* Resize the map on window.resize
(initialization only)
- * @property {string} [tileUrl]
+ * @property {string} [url]
* The open street map tile server spec default:
* http://tile.openstreetmap.org/<zoom>/<x>/<y>.png
*/
@@ -27270,7 +29636,7 @@ geo.registerWidget('d3', 'legend', geo.gui.legendWidget);
height: null,
layers: [],
data: [],
- tileUrl: 'http://tile.openstreetmap.org///.png',
+ url: 'http://tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: undefined,
// These options are for future use, but shouldn't
@@ -27304,7 +29670,7 @@ geo.registerWidget('d3', 'legend', geo.gui.legendWidget);
this.options.baseLayer,
{
renderer: this.options.baseRenderer,
- tileUrl: this.options.tileUrl,
+ url: this.options.url,
attribution: this.options.attribution
}
);
@@ -27401,9 +29767,8 @@ geo.registerWidget('d3', 'legend', geo.gui.legendWidget);
* @instance
* @param {string} url The url format string of an OSM tile server.
*/
- tileUrl: function (url) {
- this._baseLayer.tileUrl(url);
- this._baseLayer.updateBaseUrl();
+ url: function (url) {
+ this._baseLayer.url(url);
return this;
},
diff --git a/geo.min.js b/geo.min.js
index 6262699bce..7c5426102f 100644
--- a/geo.min.js
+++ b/geo.min.js
@@ -1,11 +1,12 @@
-function inherit(C,P){"use strict";var F=function(){};F.prototype=P.prototype,C.prototype=new F,C.uber=P.prototype,C.prototype.constructor=C}var geo={};if(window.geo=geo,geo.renderers={},geo.features={},geo.fileReaders={},geo.inherit=function(C,P){"use strict";var F=inherit.func();F.prototype=P.prototype,C.prototype=new F,C.prototype.constructor=C},geo.inherit.func=function(){"use strict";return function(){}},window.inherit=geo.inherit,geo.registerFileReader=function(name,func){"use strict";void 0===geo.fileReaders&&(geo.fileReaders={}),geo.fileReaders[name]=func},geo.createFileReader=function(name,opts){"use strict";return geo.fileReaders.hasOwnProperty(name)?geo.fileReaders[name](opts):null},geo.registerRenderer=function(name,func){"use strict";void 0===geo.renderers&&(geo.renderers={}),geo.renderers[name]=func},geo.createRenderer=function(name,layer,canvas,options){"use strict";if(geo.renderers.hasOwnProperty(name)){var ren=geo.renderers[name]({layer:layer,canvas:canvas,options:options});return ren._init(),ren}return null},geo.registerFeature=function(category,name,func){"use strict";void 0===geo.features&&(geo.features={}),category in geo.features||(geo.features[category]={}),geo.features[category][name]=func},geo.createFeature=function(name,layer,renderer,arg){"use strict";var category=renderer.api(),options={layer:layer,renderer:renderer};return category in geo.features&&name in geo.features[category]?(void 0!==arg&&$.extend(!0,options,arg),geo.features[category][name](options)):null},geo.registerLayer=function(name,func){"use strict";void 0===geo.layers&&(geo.layers={}),geo.layers[name]=func},geo.createLayer=function(name,map,arg){"use strict";var options={map:map,renderer:"vgl"},layer=null;return name in geo.layers?(void 0!==arg&&$.extend(!0,options,arg),layer=geo.layers[name](options),layer._init(),layer):null},geo.registerWidget=function(category,name,func){"use strict";void 0===geo.widgets&&(geo.widgets={}),category in geo.widgets||(geo.widgets[category]={}),geo.widgets[category][name]=func},geo.createWidget=function(name,layer,renderer,arg){"use strict";var category=renderer.api(),options={layer:layer,renderer:renderer};return category in geo.widgets&&name in geo.widgets[category]?(void 0!==arg&&$.extend(!0,options,arg),geo.widgets[category][name](options)):null},window.requestAnimationFrame||(window.requestAnimationFrame=function(func){"use strict";window.setTimeout(func,15)}),Math.log2||(Math.log2=function(){"use strict";return Math.log.apply(Math,arguments)/Math.LN2}),geo.version="0.5.0","undefined"==typeof ogs)var ogs={};ogs.namespace=function(ns_string){"use strict";var i,parts=ns_string.split("."),parent=ogs;for("ogs"===parts[0]&&(parts=parts.slice(1)),i=0;ithis.computeBoundsTimestamp().getMTime()&&this.m_parent.boundsDirtyTimestamp.modified(),this.computeBounds(),visitor.mode()===visitor.TraverseAllChildren)for(i=0;ithis.boundsDirtyTimestamp().getMTime()))for(i=0;ii;i+=1)istep=2*i,jstep=2*i+1,childBounds[istep]bounds[jstep]&&(bounds[jstep]=childBounds[jstep]);this.setBounds(bounds[0],bounds[1],bounds[2],bounds[3],bounds[4],bounds[5])}},this},inherit(vgl.groupNode,vgl.node),vgl.actor=function(){"use strict";if(!(this instanceof vgl.actor))return new vgl.actor;vgl.node.call(this);var m_this=this,m_transformMatrix=mat4.create(),m_referenceFrame=vgl.boundingObject.ReferenceFrame.Relative,m_mapper=null;return this.matrix=function(){return m_transformMatrix},this.setMatrix=function(tmatrix){tmatrix!==m_transformMatrix&&(m_transformMatrix=tmatrix,m_this.modified())},this.referenceFrame=function(){return m_referenceFrame},this.setReferenceFrame=function(referenceFrame){return referenceFrame!==m_referenceFrame?(m_referenceFrame=referenceFrame,m_this.modified(),!0):!1},this.mapper=function(){return m_mapper},this.setMapper=function(mapper){mapper!==m_mapper&&(m_mapper=mapper,m_this.boundsModified())},this.accept=function(visitor){visitor=visitor},this.ascend=function(visitor){visitor=visitor},this.computeLocalToWorldMatrix=function(matrix,visitor){matrix=matrix,visitor=visitor},this.computeWorldToLocalMatrix=function(matrix,visitor){matrix=matrix,visitor=visitor},this.computeBounds=function(){if(null===m_mapper||void 0===m_mapper)return void m_this.resetBounds();var mapperBounds,minPt,maxPt,newBounds,computeBoundsTimestamp=m_this.computeBoundsTimestamp();(m_this.boundsDirtyTimestamp().getMTime()>computeBoundsTimestamp.getMTime()||m_mapper.boundsDirtyTimestamp().getMTime()>computeBoundsTimestamp.getMTime())&&(m_mapper.computeBounds(),mapperBounds=m_mapper.bounds(),minPt=[mapperBounds[0],mapperBounds[2],mapperBounds[4]],maxPt=[mapperBounds[1],mapperBounds[3],mapperBounds[5]],vec3.transformMat4(minPt,minPt,m_transformMatrix),vec3.transformMat4(maxPt,maxPt,m_transformMatrix),newBounds=[minPt[0]>maxPt[0]?maxPt[0]:minPt[0],minPt[0]>maxPt[0]?minPt[0]:maxPt[0],minPt[1]>maxPt[1]?maxPt[1]:minPt[1],minPt[1]>maxPt[1]?minPt[1]:maxPt[1],minPt[2]>maxPt[2]?maxPt[2]:minPt[2],minPt[2]>maxPt[2]?minPt[2]:maxPt[2]],m_this.setBounds(newBounds[0],newBounds[1],newBounds[2],newBounds[3],newBounds[4],newBounds[5]),computeBoundsTimestamp.modified())},m_this},inherit(vgl.actor,vgl.node),vgl.freezeObject=function(obj){"use strict";var freezedObject=Object.freeze(obj);return"undefined"==typeof freezedObject&&(freezedObject=function(o){return o}),freezedObject},vgl.defaultValue=function(a,b){"use strict";return"undefined"!=typeof a?a:b},vgl.defaultValue.EMPTY_OBJECT=vgl.freezeObject({}),vgl.graphicsObject=function(type){"use strict";if(type=type,!(this instanceof vgl.graphicsObject))return new vgl.graphicsObject;vgl.object.call(this);var m_this=this;return this._setup=function(renderState){return renderState=renderState,!1},this._cleanup=function(renderState){return renderState=renderState,!1},this.bind=function(renderState){return renderState=renderState,!1},this.undoBind=function(renderState){return renderState=renderState,!1},this.render=function(renderState){return renderState=renderState,!1},this.remove=function(renderState){m_this._cleanup(renderState)},m_this},inherit(vgl.graphicsObject,vgl.object),vgl.geojsonReader=function(){"use strict";return this instanceof vgl.geojsonReader?(this.readScalars=function(coordinates,geom,size_estimate,idx){var array=null,s=null,r=null,g=null,b=null;"values"===this.m_scalarFormat&&4===coordinates.length?(s=coordinates[3],array=geom.sourceData(vgl.vertexAttributeKeys.Scalar),array||(array=new vgl.sourceDataSf,this.m_scalarRange&&array.setScalarRange(this.m_scalarRange[0],this.m_scalarRange[1]),void 0!==size_estimate&&(array.data().length=size_estimate),geom.addSource(array)),void 0===size_estimate?array.pushBack(s):array.insertAt(idx,s)):"rgb"===this.m_scalarFormat&&6===coordinates.length&&(array=geom.sourceData(vgl.vertexAttributeKeys.Color),array||(array=new vgl.sourceDataC3fv,void 0!==size_estimate&&(array.length=3*size_estimate),geom.addSource(array)),r=coordinates[3],g=coordinates[4],b=coordinates[5],void 0===size_estimate?array.pushBack([r,g,b]):array.insertAt(idx,[r,g,b]))},this.readPoint=function(coordinates){var geom=new vgl.geometryData,vglpoints=new vgl.points,vglcoords=new vgl.sourceDataP3fv,indices=new Uint16Array(1),x=null,y=null,z=null,i=null;for(geom.addSource(vglcoords),i=0;1>i;i+=1)indices[i]=i,x=coordinates[0],y=coordinates[1],z=0,coordinates.length>2&&(z=coordinates[2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates,geom);return vglpoints.setIndices(indices),geom.addPrimitive(vglpoints),geom.setName("aPoint"),geom},this.readMultiPoint=function(coordinates){var i,geom=new vgl.geometryData,vglpoints=new vgl.points,vglcoords=new vgl.sourceDataP3fv,indices=new Uint16Array(coordinates.length),pntcnt=0,estpntcnt=coordinates.length,x=null,y=null,z=null;for(vglcoords.data().length=3*estpntcnt,i=0;i2&&(z=coordinates[i][2]),vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[i],geom,estpntcnt,pntcnt),pntcnt+=1;return vglpoints.setIndices(indices),geom.addPrimitive(vglpoints),geom.addSource(vglcoords),geom.setName("manyPoints"),geom},this.readLineString=function(coordinates){var geom=new vgl.geometryData,vglline=new vgl.lineStrip,vglcoords=new vgl.sourceDataP3fv,indices=[],i=null,x=null,y=null,z=null;for(vglline.setIndicesPerPrimitive(coordinates.length),i=0;i2&&(z=coordinates[i][2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates[i],geom);return vglline.setIndices(indices),geom.addPrimitive(vglline),geom.addSource(vglcoords),geom.setName("aLineString"),geom},this.readMultiLineString=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,pntcnt=0,estpntcnt=2*coordinates.length,i=null,j=null,x=null,y=null,z=null,indices=null,vglline=null,thisLineLength=null;for(vglcoords.data().length=3*estpntcnt,j=0;ji;i+=1)indices.push(pntcnt),x=coordinates[j][i][0],y=coordinates[j][i][1],z=0,coordinates[j][i].length>2&&(z=coordinates[j][i][2]),vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[j][i],geom,2*estpntcnt,pntcnt),pntcnt+=1;vglline.setIndices(indices),geom.addPrimitive(vglline)}return geom.setName("aMultiLineString"),geom.addSource(vglcoords),geom},this.readPolygon=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,x=null,y=null,z=null,thisPolyLength=coordinates[0].length,vl=1,i=null,indices=null,vgltriangle=null;for(i=0;thisPolyLength>i;i+=1)x=coordinates[0][i][0],y=coordinates[0][i][1],z=0,coordinates[0][i].length>2&&(z=coordinates[0][i][2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates[0][i],geom),i>1&&(indices=new Uint16Array([0,vl,i]),vgltriangle=new vgl.triangles,vgltriangle.setIndices(indices),geom.addPrimitive(vgltriangle),vl=i);return geom.setName("POLY"),geom.addSource(vglcoords),geom},this.readMultiPolygon=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,ccount=0,numPolys=coordinates.length,pntcnt=0,estpntcnt=3*numPolys,vgltriangle=new vgl.triangles,indexes=[],i=null,j=null,x=null,y=null,z=null,thisPolyLength=null,vf=null,vl=null,flip=null,flipped=!1,tcount=0;for(vglcoords.data().length=3*numPolys,j=0;numPolys>j;j+=1)for(thisPolyLength=coordinates[j][0].length,vf=ccount,vl=ccount+1,flip=[!1,!1,!1],i=0;thisPolyLength>i;i+=1)x=coordinates[j][0][i][0],y=coordinates[j][0][i][1],z=0,coordinates[j][0][i].length>2&&(z=coordinates[j][0][i][2]),flipped=!1,x>180&&(flipped=!0,x-=360),0===i?flip[0]=flipped:flip[1+(i-1)%2]=flipped,vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[j][0][i],geom,estpntcnt,pntcnt),pntcnt+=1,i>1&&(flip[0]===flip[1]&&flip[1]===flip[2]&&(indexes[3*tcount+0]=vf,indexes[3*tcount+1]=vl,indexes[3*tcount+2]=ccount,tcount+=1),vl=ccount),ccount+=1;return vgltriangle.setIndices(indexes),geom.addPrimitive(vgltriangle),geom.setName("aMultiPoly"),geom.addSource(vglcoords),geom},this.readGJObjectInt=function(object){if(!object.hasOwnProperty("type"))return null;object.properties&&object.properties.ScalarFormat&&"values"===object.properties.ScalarFormat&&(this.m_scalarFormat="values",object.properties.ScalarRange&&(this.m_scalarRange=object.properties.ScalarRange)),object.properties&&object.properties.ScalarFormat&&"rgb"===object.properties.ScalarFormat&&(this.m_scalarFormat="rgb");var ret,type=object.type,next=null,nextset=null,i=null;switch(type){case"Point":ret=this.readPoint(object.coordinates);break;case"MultiPoint":ret=this.readMultiPoint(object.coordinates);break;case"LineString":ret=this.readLineString(object.coordinates);break;case"MultiLineString":ret=this.readMultiLineString(object.coordinates);break;case"Polygon":ret=this.readPolygon(object.coordinates);break;case"MultiPolygon":ret=this.readMultiPolygon(object.coordinates);break;case"GeometryCollection":for(nextset=[],i=0;im_max)&&(m_max=value),(null===m_min||m_min>value)&&(m_min=value),this.data()[this.data().length]=value},this.insertAt=function(index,value){(null===m_max||value>m_max)&&(m_max=value),(null===m_min||m_min>value)&&(m_min=value),this.data()[index]=value},this.scalarRange=function(){return null===m_fixedmin||null===m_fixedmax?[m_min,m_max]:[m_fixedmin,m_fixedmax]},this.setScalarRange=function(min,max){m_fixedmin=min,m_fixedmax=max},this},inherit(vgl.sourceDataSf,vgl.sourceData),vgl.sourceDataDf=function(arg){"use strict";return this instanceof vgl.sourceDataDf?(vgl.sourceData.call(this,arg),this.addAttribute(vgl.vertexAttributeKeys.Scalar,vgl.GL.FLOAT,4,0,4,1,!1),this.pushBack=function(value){this.data()[this.data().length]=value},this.insertAt=function(index,value){this.data()[index]=value},this):new vgl.sourceDataDf(arg)},inherit(vgl.sourceDataDf,vgl.sourceData),vgl.geometryData=function(){"use strict";if(!(this instanceof vgl.geometryData))return new vgl.geometryData;vgl.data.call(this);var m_name="",m_primitives=[],m_sources=[],m_bounds=[0,0,0,0,0,0],m_computeBoundsTimestamp=vgl.timestamp(),m_boundsDirtyTimestamp=vgl.timestamp();return this.type=function(){return vgl.data.geometry},this.name=function(){return m_name},this.setName=function(name){m_name=name},this.addSource=function(source,sourceName){return void 0!==sourceName&&source.setName(sourceName),-1===m_sources.indexOf(source)?(m_sources.push(source),source.hasKey(vgl.vertexAttributeKeys.Position)&&m_boundsDirtyTimestamp.modified(),!0):!1},this.source=function(index){return indexm_computeBoundsTimestamp.getMTime()&&this.computeBounds(),m_bounds},this.boundsDirty=function(dirty){return dirty&&m_boundsDirtyTimestamp.modified(),m_boundsDirtyTimestamp.getMTime()>m_computeBoundsTimestamp.getMTime()},this.resetBounds=function(){m_bounds[0]=0,m_bounds[1]=0,m_bounds[2]=0,m_bounds[3]=0,m_bounds[4]=0,m_bounds[5]=0},this.setBounds=function(minX,maxX,minY,maxY,minZ,maxZ){return m_bounds[0]=minX,m_bounds[1]=maxX,m_bounds[2]=minY,m_bounds[3]=maxY,m_bounds[4]=minZ,m_bounds[5]=maxZ,m_computeBoundsTimestamp.modified(),!0},this.computeBounds=function(){if(m_boundsDirtyTimestamp.getMTime()>m_computeBoundsTimestamp.getMTime()){var j,ib,jb,maxv,minv,vertexIndex,attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),data=sourceData.data(),numberOfComponents=sourceData.attributeNumberOfComponents(attr),stride=sourceData.attributeStride(attr),offset=sourceData.attributeOffset(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),count=data.length,value=null;for(stride/=sizeOfDataType,offset/=sizeOfDataType,this.resetBounds(),j=0;numberOfComponents>j;j+=1){for(ib=2*j,jb=2*j+1,maxv=minv=count?m_bounds[jb]=data[offset+j]:0,vertexIndex=offset+stride+j;count>vertexIndex;vertexIndex+=stride)value=data[vertexIndex],value>maxv&&(maxv=value),minv>value&&(minv=value);m_bounds[ib]=minv,m_bounds[jb]=maxv}m_computeBoundsTimestamp.modified()}},this.findClosestVertex=function(point){var vi,vPos,dx,dy,dz,dist,i,attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),numberOfComponents=sourceData.attributeNumberOfComponents(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType,minDist=Number.MAX_VALUE,minIndex=null;for(3!==numberOfComponents&&console.log("[warning] Find closest vertex assumes threecomponent vertex "),point.z||(point={x:point.x,y:point.y,z:0}),vi=offset,i=0;vidist&&(minDist=dist,minIndex=i);return minIndex},this.getPosition=function(index){var attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),numberOfComponents=sourceData.attributeNumberOfComponents(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType;return 3!==numberOfComponents&&console.log("[warning] getPosition assumes three component data"),[data[offset+index*stride],data[offset+index*stride+1],data[offset+index*stride+2]]},this.getScalar=function(index){var numberOfComponents,sizeOfDataType,data,stride,offset,attr=vgl.vertexAttributeKeys.Scalar,sourceData=this.sourceData(attr);return sourceData?(numberOfComponents=sourceData.attributeNumberOfComponents(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType,index*stride+offset>=data.length&&console.log("access out of bounds in getScalar"),data[index*stride+offset]):null},this},inherit(vgl.geometryData,vgl.data),vgl.mapper=function(arg){"use strict";function deleteVertexBufferObjects(renderState){var i;for(i=0;ii;i+=1){for(bufferId=m_context.createBuffer(),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,bufferId),data=m_geomData.source(i).data(),data instanceof Float32Array||(data=new Float32Array(data)),m_context.bufferData(vgl.GL.ARRAY_BUFFER,data,m_dynamicDraw?vgl.GL.DYNAMIC_DRAW:vgl.GL.STATIC_DRAW),keys=m_geomData.source(i).keys(),ks=[],j=0;jk;k+=1)bufferId=m_context.createBuffer(),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,bufferId),m_context.bufferData(vgl.GL.ARRAY_BUFFER,m_geomData.primitive(k).indices(),vgl.GL.STATIC_DRAW),m_buffers[i]=bufferId,i+=1;m_glCompileTimestamp.modified()}}function cleanUpDrawObjects(renderState){renderState=renderState,m_bufferVertexAttributeMap={},m_buffers=[]}function setupDrawObjects(renderState){deleteVertexBufferObjects(renderState),cleanUpDrawObjects(renderState),createVertexBufferObjects(renderState),m_dirty=!1}if(!(this instanceof vgl.mapper))return new vgl.mapper(arg);vgl.boundingObject.call(this),arg=arg||{};var m_dirty=!0,m_color=[0,1,1],m_geomData=null,m_buffers=[],m_bufferVertexAttributeMap={},m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,m_glCompileTimestamp=vgl.timestamp(),m_context=null;return this.computeBounds=function(){if(null===m_geomData||"undefined"==typeof m_geomData)return void this.resetBounds();var computeBoundsTimestamp=this.computeBoundsTimestamp(),boundsDirtyTimestamp=this.boundsDirtyTimestamp(),geomBounds=null;boundsDirtyTimestamp.getMTime()>computeBoundsTimestamp.getMTime()&&(geomBounds=m_geomData.bounds(),this.setBounds(geomBounds[0],geomBounds[1],geomBounds[2],geomBounds[3],geomBounds[4],geomBounds[5]),computeBoundsTimestamp.modified())},this.color=function(){return m_color},this.setColor=function(r,g,b){m_color[0]=r,m_color[1]=g,m_color[2]=b,this.modified()},this.geometryData=function(){return m_geomData},this.setGeometryData=function(geom){m_geomData!==geom&&(m_geomData=geom,this.modified(),this.boundsDirtyTimestamp().modified())},this.updateSourceBuffer=function(sourceName,values,renderState){if(renderState&&(m_context=renderState.m_context),!m_context)return!1;for(var bufferIndex=-1,i=0;ibufferIndex||bufferIndex>=m_buffers.length?!1:(values||(values=m_geomData.source(i).dataToFloat32Array()),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),values instanceof Float32Array?m_context.bufferSubData(vgl.GL.ARRAY_BUFFER,0,values):m_context.bufferSubData(vgl.GL.ARRAY_BUFFER,0,new Float32Array(values)),!0)},this.getSourceBuffer=function(sourceName){var source=m_geomData.sourceByName(sourceName);return source?source.dataToFloat32Array():new Float32Array},this.render=function(renderState){(this.getMTime()>m_glCompileTimestamp.getMTime()||renderState.m_contextChanged)&&setupDrawObjects(renderState),m_context=renderState.m_context,m_context.vertexAttrib3fv(vgl.vertexAttributeKeys.Color,this.color());var i,bufferIndex=0,j=0,noOfPrimitives=null,primitive=null;for(i in m_bufferVertexAttributeMap)if(m_bufferVertexAttributeMap.hasOwnProperty(i)){for(m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),j=0;jj;j+=1){switch(m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),bufferIndex+=1,primitive=m_geomData.primitive(j),primitive.primitiveType()){case vgl.GL.POINTS:m_context.drawArrays(vgl.GL.POINTS,0,primitive.numberOfIndices());break;case vgl.GL.LINES:m_context.drawArrays(vgl.GL.LINES,0,primitive.numberOfIndices());break;case vgl.GL.LINE_STRIP:m_context.drawArrays(vgl.GL.LINE_STRIP,0,primitive.numberOfIndices());break;case vgl.GL.TRIANGLES:m_context.drawArrays(vgl.GL.TRIANGLES,0,primitive.numberOfIndices());break;case vgl.GL.TRIANGLE_STRIP:m_context.drawArrays(vgl.GL.TRIANGLE_STRIP,0,primitive.numberOfIndices())}m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,null)}},this},inherit(vgl.mapper,vgl.boundingObject),vgl.groupMapper=function(){"use strict";if(!(this instanceof vgl.groupMapper))return new vgl.groupMapper;vgl.mapper.call(this);var m_createMappersTimestamp=vgl.timestamp(),m_mappers=[],m_geomDataArray=[];return this.geometryData=function(index){return void 0!==index&&index0?m_geomDataArray[0]:null},this.setGeometryData=function(geom){(1!==m_geomDataArray.length||m_geomDataArray[0]!==geom)&&(m_geomDataArray=[],m_geomDataArray.push(geom),this.modified())},this.geometryDataArray=function(){return m_geomDataArray},this.setGeometryDataArray=function(geoms){if(geoms instanceof Array){if(m_geomDataArray!==geoms)return m_geomDataArray=[],m_geomDataArray=geoms,this.modified(),!0}else console.log("[error] Requies array of geometry data");return!1},this.computeBounds=function(){if(null===m_geomDataArray||void 0===m_geomDataArray)return void this.resetBounds();var computeBoundsTimestamp=this.computeBoundsTimestamp(),boundsDirtyTimestamp=this.boundsDirtyTimestamp(),m_bounds=this.bounds(),geomBounds=null,i=null;if(boundsDirtyTimestamp.getMTime()>computeBoundsTimestamp.getMTime()){for(i=0;igeomBounds[0]&&(m_bounds[0]=geomBounds[0]),m_bounds[1]geomBounds[2]&&(m_bounds[2]=geomBounds[2]),m_bounds[3]geomBounds[4]&&(m_bounds[4]=geomBounds[4]),m_bounds[5]m_createMappersTimestamp.getMTime()){for(i=0;i0&&m_this.m_resetScene&&(m_this.resetCamera(),m_this.m_resetScene=!1),i=0;i=0&&sortedActors.push([actor.material().binNumber(),actor]);for(sortedActors.sort(function(a,b){return a[0]-b[0]}),i=0;idiagonals[1]?diagonals[0]>diagonals[2]?diagonals[0]/2:diagonals[2]/2:diagonals[1]>diagonals[2]?diagonals[1]/2:diagonals[2]/2,angle=aspect>=1?2*Math.atan(Math.tan(.5*angle)/aspect):2*Math.atan(Math.tan(.5*angle)*aspect),distance=radius/Math.sin(.5*angle),vup=m_this.m_camera.viewUpDirection(),Math.abs(vec3.dot(vup,vn))>.999&&m_this.m_camera.setViewUpDirection(-vup[2],vup[0],vup[1]),m_this.m_camera.setFocalPoint(center[0],center[1],center[2]),m_this.m_camera.setPosition(center[0]+distance*-vn[0],center[1]+distance*-vn[1],center[2]+distance*-vn[2]),m_this.resetCameraClippingRange(visibleBounds)},this.hasValidBounds=function(bounds){return bounds[0]===Number.MAX_VALUE||bounds[1]===-Number.MAX_VALUE||bounds[2]===Number.MAX_VALUE||bounds[3]===-Number.MAX_VALUE||bounds[4]===Number.MAX_VALUE||bounds[5]===-Number.MAX_VALUE?!1:!0},this.resetCameraClippingRange=function(bounds){if("undefined"==typeof bounds&&(m_this.m_camera.computeBounds(),bounds=m_this.m_camera.bounds()),m_this.hasValidBounds(bounds)){var vn=m_this.m_camera.viewPlaneNormal(),position=m_this.m_camera.position(),a=-vn[0],b=-vn[1],c=-vn[2],d=-(a*position[0]+b*position[1]+c*position[2]),range=vec2.create(),dist=null,i=null,j=null,k=null;if(m_this.m_resetClippingRange){for(range[0]=a*bounds[0]+b*bounds[2]+c*bounds[4]+d,range[1]=1e-18,k=0;2>k;k+=1)for(j=0;2>j;j+=1)for(i=0;2>i;i+=1)dist=a*bounds[i]+b*bounds[2+j]+c*bounds[4+k]+d,range[0]=distrange[1]?dist:range[1];range[0]<0&&(range[0]=0),range[0]=.99*range[0]-.5*(range[1]-range[0]),range[1]=1.01*range[1]+.5*(range[1]-range[0]),range[0]=range[0]>=range[1]?.01*range[1]:range[0],m_this.m_nearClippingPlaneTolerance||(m_this.m_nearClippingPlaneTolerance=.01,m_this.m_depthBits&&m_this.m_depthBits>16&&(m_this.m_nearClippingPlaneTolerance=.001)),range[0]x||0>y||0>=width||0>=height)return void console.log("[error] Invalid position and resize values",x,y,width,height);if(m_this.m_resizable&&(m_this.m_width=width,m_this.m_height=height,m_this.m_camera.setViewAspect(width/height),m_this.m_camera.setParallelExtents({width:width,height:height}),m_this.modified()),m_this.m_renderPasses)for(i=0;im_width||0===m_renderers[i].width()||m_renderers[i].height()>m_height||0===m_renderers[i].height())&&m_renderers[i].resize(m_x,m_y,m_width,m_height);return!0}catch(e){}return m_context||console("[ERROR] Unable to initialize WebGL. Your browser may not support it."),!1},this.context=function(){return m_context},this._cleanup=function(renderState){var i;for(i=0;iwidth?(m_currPos.x=0,m_outsideCanvas=!0):m_currPos.x=coords.x,coords.y<0||coords.y>height?(m_currPos.y=0,m_outsideCanvas=!0):m_currPos.y=coords.y,m_outsideCanvas!==!0?(fp=cam.focalPoint(),fwp=vec4.fromValues(fp[0],fp[1],fp[2],1),fdp=ren.worldToDisplay(fwp,cam.viewMatrix(),cam.projectionMatrix(),width,height),dp1=vec4.fromValues(m_currPos.x,m_currPos.y,fdp[2],1),dp2=vec4.fromValues(m_lastPos.x,m_lastPos.y,fdp[2],1),wp1=ren.displayToWorld(dp1,cam.viewMatrix(),cam.projectionMatrix(),width,height),wp2=ren.displayToWorld(dp2,cam.viewMatrix(),cam.projectionMatrix(),width,height),dx=wp1[0]-wp2[0],dy=wp1[1]-wp2[1],dz=wp1[2]-wp2[2],m_midMouseBtnDown&&(cam.pan(-dx,-dy,-dz),m_that.viewer().render()),m_leftMouseBtnDown&&(cam.rotate(m_lastPos.x-m_currPos.x,m_lastPos.y-m_currPos.y),ren.resetCameraClippingRange(),m_that.viewer().render()),m_rightMouseBtnDown&&(m_zTrans=2*(m_currPos.y-m_lastPos.y)/height,m_zTrans>0?cam.zoom(1-Math.abs(m_zTrans)):cam.zoom(1+Math.abs(m_zTrans)),ren.resetCameraClippingRange(),m_that.viewer().render()),m_lastPos.x=m_currPos.x,m_lastPos.y=m_currPos.y,!1):void 0},this.handleMouseDown=function(event){var coords;return 0===event.button&&(m_leftMouseBtnDown=!0),1===event.button&&(m_midMouseBtnDown=!0),2===event.button&&(m_rightMouseBtnDown=!0),coords=m_that.viewer().relMouseCoords(event),coords.x<0?m_lastPos.x=0:m_lastPos.x=coords.x,coords.y<0?m_lastPos.y=0:m_lastPos.y=coords.y,!1},this.handleMouseUp=function(event){return 0===event.button&&(m_leftMouseBtnDown=!1),1===event.button&&(m_midMouseBtnDown=!1),2===event.button&&(m_rightMouseBtnDown=!1),!1},this.handleMouseWheel=function(event){var ren=m_that.viewer().renderWindow().activeRenderer(),cam=ren.camera();return event.originalEvent.wheelDelta<0?cam.zoom(.9):cam.zoom(1.1),ren.resetCameraClippingRange(),m_that.viewer().render(),!0},this},inherit(vgl.trackballInteractorStyle,vgl.interactorStyle),vgl.pvwInteractorStyle=function(){"use strict";function render(){m_renderer.resetCameraClippingRange(),m_that.viewer().render()}if(!(this instanceof vgl.pvwInteractorStyle))return new vgl.pvwInteractorStyle;vgl.trackballInteractorStyle.call(this);var m_width,m_height,m_renderer,m_camera,m_outsideCanvas,m_coords,m_currentMousePos,m_focalPoint,m_focusWorldPt,m_focusDisplayPt,m_displayPt1,m_displayPt2,m_worldPt1,m_worldPt2,m_dx,m_dy,m_dz,m_zTrans,m_that=this,m_leftMouseButtonDown=!1,m_rightMouseButtonDown=!1,m_middleMouseButtonDown=!1,m_mouseLastPos={x:0,y:0};return this.handleMouseMove=function(event){var rens=[],i=null,secCameras=[],deltaxy=null;for(m_width=m_that.viewer().renderWindow().windowSize()[0],m_height=m_that.viewer().renderWindow().windowSize()[1],m_renderer=m_that.viewer().renderWindow().activeRenderer(),m_camera=m_renderer.camera(),m_outsideCanvas=!1,m_coords=m_that.viewer().relMouseCoords(event),m_currentMousePos={x:0,y:0},rens=m_that.viewer().renderWindow().renderers(),i=0;im_width?(m_currentMousePos.x=0,m_outsideCanvas=!0):m_currentMousePos.x=m_coords.x,m_coords.y<0||m_coords.y>m_height?(m_currentMousePos.y=0,m_outsideCanvas=!0):m_currentMousePos.y=m_coords.y,m_outsideCanvas!==!0){if(m_focalPoint=m_camera.focalPoint(),m_focusWorldPt=vec4.fromValues(m_focalPoint[0],m_focalPoint[1],m_focalPoint[2],1),m_focusDisplayPt=m_renderer.worldToDisplay(m_focusWorldPt,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_displayPt1=vec4.fromValues(m_currentMousePos.x,m_currentMousePos.y,m_focusDisplayPt[2],1),m_displayPt2=vec4.fromValues(m_mouseLastPos.x,m_mouseLastPos.y,m_focusDisplayPt[2],1),m_worldPt1=m_renderer.displayToWorld(m_displayPt1,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_worldPt2=m_renderer.displayToWorld(m_displayPt2,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_dx=m_worldPt1[0]-m_worldPt2[0],m_dy=m_worldPt1[1]-m_worldPt2[1],m_dz=m_worldPt1[2]-m_worldPt2[2],m_middleMouseButtonDown&&(m_camera.pan(-m_dx,-m_dy,-m_dz),render()),m_leftMouseButtonDown){for(deltaxy=[m_mouseLastPos.x-m_currentMousePos.x,m_mouseLastPos.y-m_currentMousePos.y],m_camera.rotate(deltaxy[0],deltaxy[1]),i=0;i0?m_camera.zoom(1-Math.abs(m_zTrans)):m_camera.zoom(1+Math.abs(m_zTrans)),render()),m_mouseLastPos.x=m_currentMousePos.x,m_mouseLastPos.y=m_currentMousePos.y,!1}},this.handleMouseDown=function(event){return 0===event.button&&(m_leftMouseButtonDown=!0),1===event.button&&(m_middleMouseButtonDown=!0),2===event.button&&(m_rightMouseButtonDown=!0),m_coords=m_that.viewer().relMouseCoords(event),m_coords.x<0?m_mouseLastPos.x=0:m_mouseLastPos.x=m_coords.x,m_coords.y<0?m_mouseLastPos.y=0:m_mouseLastPos.y=m_coords.y,!1},this.handleMouseUp=function(event){return 0===event.button&&(m_leftMouseButtonDown=!1),1===event.button&&(m_middleMouseButtonDown=!1),2===event.button&&(m_rightMouseButtonDown=!1),!1},this},inherit(vgl.pvwInteractorStyle,vgl.trackballInteractorStyle),vgl.viewer=function(canvas,options){"use strict";if(!(this instanceof vgl.viewer))return new vgl.viewer(canvas,options);vgl.object.call(this);var m_that=this,m_canvas=canvas,m_ready=!0,m_interactorStyle=null,m_renderer=vgl.renderer(options),m_renderWindow=vgl.renderWindow(m_canvas);return this.canvas=function(){return m_canvas},this.renderWindow=function(){return m_renderWindow},this.init=function(){null!==m_renderWindow?m_renderWindow._setup():console.log("[ERROR] No render window attached")},this.exit=function(renderState){null!==m_renderWindow?m_renderWindow._cleanup(renderState):console.log("[ERROR] No render window attached")},this.interactorStyle=function(){return m_interactorStyle},this.setInteractorStyle=function(style){style!==m_interactorStyle&&(m_interactorStyle=style,m_interactorStyle.setViewer(this),this.modified())},this.handleMouseDown=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);2===event.button&&fixedEvent.preventDefault(),fixedEvent.state="down",fixedEvent.type=vgl.event.mousePress,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseUp=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.state="up",fixedEvent.type=vgl.event.mouseRelease,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseMove=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseMove,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseWheel=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseWheel,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseOut=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseOut,$(m_that).trigger(fixedEvent)}return!0},this.handleKeyPress=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.keyPress,$(m_that).trigger(fixedEvent)}return!0},this.handleContextMenu=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.contextMenu,$(m_that).trigger(fixedEvent)}return!1},this.handleClick=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.click,$(m_that).trigger(fixedEvent)}return!1},this.handleDoubleClick=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.dblClick,$(m_that).trigger(fixedEvent)}return!1},this.relMouseCoords=function(event){if(void 0===event.pageX||void 0===event.pageY)throw"Missing attributes pageX and pageY on the event";var totalOffsetX=0,totalOffsetY=0,canvasX=0,canvasY=0,currentElement=m_canvas;do totalOffsetX+=currentElement.offsetLeft-currentElement.scrollLeft,totalOffsetY+=currentElement.offsetTop-currentElement.scrollTop,currentElement=currentElement.offsetParent;while(currentElement);return canvasX=event.pageX-totalOffsetX,canvasY=event.pageY-totalOffsetY,{x:canvasX,y:canvasY}},this.render=function(){m_renderWindow.render()},this.bindEventHandlers=function(){$(m_canvas).on("mousedown",this.handleMouseDown),$(m_canvas).on("mouseup",this.handleMouseUp),$(m_canvas).on("mousemove",this.handleMouseMove),$(m_canvas).on("mousewheel",this.handleMouseWheel),$(m_canvas).on("contextmenu",this.handleContextMenu)},this.unbindEventHandlers=function(){$(m_canvas).off("mousedown",this.handleMouseDown),$(m_canvas).off("mouseup",this.handleMouseUp),$(m_canvas).off("mousemove",this.handleMouseMove),$(m_canvas).off("mousewheel",this.handleMouseWheel),$(m_canvas).off("contextmenu",this.handleContextMenu)},this._init=function(){this.bindEventHandlers(),m_renderWindow.addRenderer(m_renderer)},this._init(),this},inherit(vgl.viewer,vgl.object),vgl.shader=function(type){"use strict";if(!(this instanceof vgl.shader))return new vgl.shader(type);vgl.object.call(this);var m_shaderHandle=null,m_compileTimestamp=vgl.timestamp(),m_shaderType=type,m_shaderSource="";this.shaderHandle=function(){return m_shaderHandle},this.shaderType=function(){return m_shaderType},this.shaderSource=function(){return m_shaderSource},this.setShaderSource=function(source){m_shaderSource=source,this.modified()},this.compile=function(renderState){return this.getMTime()-1)return!1;var i;for(i=0;i-1?!1:(m_uniforms.push(uniform),void m_this.modified())},this.addVertexAttribute=function(attr,key){m_vertexAttributes[key]=attr,m_this.modified()},this.uniformLocation=function(name){return m_uniformNameToLocation[name]},this.attributeLocation=function(name){return m_vertexAttributeNameToLocation[name]},this.uniform=function(name){var i;for(i=0;i=this.getMTime())){for(m_this._setup(renderState),i=0;im_setupTimestamp.getMTime()&&this.setup(renderState),activateTextureUnit(renderState),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,this.m_textureHandle)},this.undoBind=function(renderState){renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,null)},this.image=function(){return this.m_image},this.setImage=function(image){return null!==image?(this.m_image=image,this.updateDimensions(),this.modified(),!0):!1},this.nearestPixel=function(){return this.m_nearestPixel},this.setNearestPixel=function(nearest){return nearest=nearest?!0:!1,nearest!==this.m_nearestPixel?(this.m_nearestPixel=nearest,this.modified(),!0):!1},this.textureUnit=function(){return this.m_textureUnit},this.setTextureUnit=function(unit){return this.m_textureUnit===unit?!1:(this.m_textureUnit=unit,this.modified(),!0)},this.width=function(){return this.m_width},this.setWidth=function(width){return m_that.m_width!==width?(m_that.m_width=width,m_that.modified(),!0):!1},this.height=function(){return m_that.m_height},this.setHeight=function(height){return m_that.m_height!==height?(m_that.m_height=height,m_that.modified(),!0):!1},this.depth=function(){return this.m_depth},this.setDepth=function(depth){return null===this.m_image?!1:(this.m_depth=depth,this.modified(),!0)},this.textureHandle=function(){return this.m_textureHandle},this.internalFormat=function(){return this.m_internalFormat},this.setInternalFormat=function(internalFormat){return this.m_internalFormat!==internalFormat?(this.m_internalFormat=internalFormat,this.modified(),!0):!1},this.pixelFormat=function(){return this.m_pixelFormat},this.setPixelFormat=function(pixelFormat){return null===this.m_image?!1:(this.m_pixelFormat=pixelFormat,this.modified(),!0)},this.pixelDataType=function(){return this.m_pixelDataType},this.setPixelDataType=function(pixelDataType){return null===this.m_image?!1:(this.m_pixelDataType=pixelDataType,this.modified(),!0)},this.computeInternalFormatUsingImage=function(){this.m_internalFormat=vgl.GL.RGBA,this.m_pixelFormat=vgl.GL.RGBA,this.m_pixelDataType=vgl.GL.UNSIGNED_BYTE},this.updateDimensions=function(){null!==this.m_image&&(this.m_width=this.m_image.width,this.m_height=this.m_image.height,this.m_depth=0)},this},inherit(vgl.texture,vgl.materialAttribute),vgl.lookupTable=function(){"use strict";if(!(this instanceof vgl.lookupTable))return new vgl.lookupTable;vgl.texture.call(this);var m_setupTimestamp=vgl.timestamp(),m_range=[0,0];return this.m_colorTable=[.07514311,.468049805,1,1,.247872569,.498782363,1,1,.339526309,.528909511,1,1,.409505078,.558608486,1,1,.468487184,.588057293,1,1,.520796675,.617435078,1,1,.568724526,.646924167,1,1,.613686735,.676713218,1,1,.656658579,.707001303,1,1,.698372844,.738002964,1,1,.739424025,.769954435,1,1,.780330104,.803121429,1,1,.821573924,.837809045,1,1,.863634967,.874374691,1,1,.907017747,.913245283,1,1,.936129275,.938743558,.983038586,1,.943467973,.943498599,.943398095,1,.990146732,.928791426,.917447482,1,1,.88332677,.861943246,1,1,.833985467,.803839606,1,1,.788626485,.750707739,1,1,.746206642,.701389973,1,1,.70590052,.654994046,1,1,.667019783,.610806959,1,1,.6289553,.568237474,1,1,.591130233,.526775617,1,1,.552955184,.485962266,1,1,.513776083,.445364274,1,1,.472800903,.404551679,1,1,.428977855,.363073592,1,1,.380759558,.320428137,1,.961891484,.313155629,.265499262,1,.916482116,.236630659,.209939162,1].map(function(x){
-return 255*x}),this.setup=function(renderState){0===this.textureUnit()?renderState.m_context.activeTexture(vgl.GL.TEXTURE0):1===this.textureUnit()&&renderState.m_context.activeTexture(vgl.GL.TEXTURE1),renderState.m_context.deleteTexture(this.m_textureHandle),this.m_textureHandle=renderState.m_context.createTexture(),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,this.m_textureHandle),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_MIN_FILTER,vgl.GL.LINEAR),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_MAG_FILTER,vgl.GL.LINEAR),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_WRAP_S,vgl.GL.CLAMP_TO_EDGE),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_WRAP_T,vgl.GL.CLAMP_TO_EDGE),renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT,1),this.m_width=this.m_colorTable.length/4,this.m_height=1,this.m_depth=0,renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D,0,vgl.GL.RGBA,this.m_width,this.m_height,this.m_depth,vgl.GL.RGBA,vgl.GL.UNSIGNED_BYTE,new Uint8Array(this.m_colorTable)),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,null),m_setupTimestamp.modified()},this.colorTable=function(){return this.m_colorTable},this.setColorTable=function(colors){return this.m_colorTable===colors?!1:(this.m_colorTable=colors,this.modified(),!0)},this.range=function(){return m_range},this.setRange=function(range){return m_range===range?!1:(m_range=range,this.modified(),!0)},this.updateRange=function(range){range instanceof Array||console.log("[error] Invalid data type for range. Requires array [min,max]"),range[0]m_range[1]&&(m_range[1]=range[1],this.modified())},this},inherit(vgl.lookupTable,vgl.texture),vgl.uniform=function(type,name){"use strict";if(!(this instanceof vgl.uniform))return new vgl.uniform;this.getTypeNumberOfComponents=function(type){switch(type){case vgl.GL.FLOAT:case vgl.GL.INT:case vgl.GL.BOOL:return 1;case vgl.GL.FLOAT_VEC2:case vgl.GL.INT_VEC2:case vgl.GL.BOOL_VEC2:return 2;case vgl.GL.FLOAT_VEC3:case vgl.GL.INT_VEC3:case vgl.GL.BOOL_VEC3:return 3;case vgl.GL.FLOAT_VEC4:case vgl.GL.INT_VEC4:case vgl.GL.BOOL_VEC4:return 4;case vgl.GL.FLOAT_MAT3:return 9;case vgl.GL.FLOAT_MAT4:return 16;default:return 0}};var m_type=type,m_name=name,m_dataArray=[];return m_dataArray.length=this.getTypeNumberOfComponents(m_type),this.name=function(){return m_name},this.type=function(){return m_type},this.get=function(){return m_dataArray},this.set=function(value){var i=0;if(16===m_dataArray.length)for(i=0;16>i;i+=1)m_dataArray[i]=value[i];else if(9===m_dataArray.length)for(i=0;9>i;i+=1)m_dataArray[i]=value[i];else if(4===m_dataArray.length)for(i=0;4>i;i+=1)m_dataArray[i]=value[i];else if(3===m_dataArray.length)for(i=0;3>i;i+=1)m_dataArray[i]=value[i];else if(2===m_dataArray.length)for(i=0;2>i;i+=1)m_dataArray[i]=value[i];else m_dataArray[0]=value},this.callGL=function(renderState,location){if(!(this.m_numberElements<1))switch(m_type){case vgl.GL.BOOL:case vgl.GL.INT:renderState.m_context.uniform1iv(location,m_dataArray);break;case vgl.GL.FLOAT:renderState.m_context.uniform1fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC2:renderState.m_context.uniform2fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC3:renderState.m_context.uniform3fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC4:renderState.m_context.uniform4fv(location,m_dataArray);break;case vgl.GL.FLOAT_MAT3:renderState.m_context.uniformMatrix3fv(location,vgl.GL.FALSE,m_dataArray);break;case vgl.GL.FLOAT_MAT4:renderState.m_context.uniformMatrix4fv(location,vgl.GL.FALSE,m_dataArray)}},this.update=function(renderState,program){renderState=renderState,program=program},this},vgl.modelViewUniform=function(name){"use strict";return this instanceof vgl.modelViewUniform?(0===name.length&&(name="modelViewMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_modelViewMatrix)},this):new vgl.modelViewUniform(name)},inherit(vgl.modelViewUniform,vgl.uniform),vgl.modelViewOriginUniform=function(name,origin){"use strict";if(!(this instanceof vgl.modelViewOriginUniform))return new vgl.modelViewOriginUniform(name,origin);0===name.length&&(name="modelViewMatrix");var m_origin=vec3.fromValues(origin[0],origin[1],origin[2]);return vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program;var view=mat4.create();if(mat4.translate(view,renderState.m_modelViewMatrix,m_origin),renderState.m_modelViewAlignment){var align=renderState.m_modelViewAlignment;view[12]=Math.round(view[12]/align.round)*align.round+align.dx,view[13]=Math.round(view[13]/align.round)*align.round+align.dy}this.set(view)},this},inherit(vgl.modelViewOriginUniform,vgl.uniform),vgl.projectionUniform=function(name){"use strict";return this instanceof vgl.projectionUniform?(0===name.length&&(name="projectionMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_projectionMatrix)},this):new vgl.projectionUniform(name)},inherit(vgl.projectionUniform,vgl.uniform),vgl.floatUniform=function(name,value){"use strict";return this instanceof vgl.floatUniform?(0===name.length&&(name="floatUniform"),value=void 0===value?1:value,vgl.uniform.call(this,vgl.GL.FLOAT,name),void this.set(value)):new vgl.floatUniform(name,value)},inherit(vgl.floatUniform,vgl.uniform),vgl.normalMatrixUniform=function(name){"use strict";return this instanceof vgl.normalMatrixUniform?(0===name.length&&(name="normalMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_normalMatrix)},this):new vgl.normalMatrixUniform(name)},inherit(vgl.normalMatrixUniform,vgl.uniform),vgl.vertexAttributeKeys={Position:0,Normal:1,TextureCoordinate:2,Color:3,Scalar:4,CountAttributeIndex:5},vgl.vertexAttributeKeysIndexed={Zero:0,One:1,Two:2,Three:3,Four:4,Five:5,Six:6,Seven:7,Eight:8,Nine:9},vgl.vertexAttribute=function(name){"use strict";if(!(this instanceof vgl.vertexAttribute))return new vgl.vertexAttribute(name);var m_name=name;this.name=function(){return m_name},this.bindVertexData=function(renderState,key){var geometryData=renderState.m_mapper.geometryData(),sourceData=geometryData.sourceData(key),program=renderState.m_material.shaderProgram();renderState.m_context.vertexAttribPointer(program.attributeLocation(m_name),sourceData.attributeNumberOfComponents(key),sourceData.attributeDataType(key),sourceData.normalized(key),sourceData.attributeStride(key),sourceData.attributeOffset(key)),renderState.m_context.enableVertexAttribArray(program.attributeLocation(m_name))},this.undoBindVertexData=function(renderState,key){key=key;var program=renderState.m_material.shaderProgram();renderState.m_context.disableVertexAttribArray(program.attributeLocation(m_name))}},vgl.source=function(){"use strict";return this instanceof vgl.source?(vgl.object.call(this),this.create=function(){},this):new vgl.source},inherit(vgl.source,vgl.object),vgl.planeSource=function(){"use strict";if(!(this instanceof vgl.planeSource))return new vgl.planeSource;vgl.source.call(this);var m_origin=[0,0,0],m_point1=[1,0,0],m_point2=[0,1,0],m_normal=[0,0,1],m_xresolution=1,m_yresolution=1,m_geom=null;this.setOrigin=function(x,y,z){m_origin[0]=x,m_origin[1]=y,m_origin[2]=z},this.setPoint1=function(x,y,z){m_point1[0]=x,m_point1[1]=y,m_point1[2]=z},this.setPoint2=function(x,y,z){m_point2[0]=x,m_point2[1]=y,m_point2[2]=z},this.create=function(){m_geom=new vgl.geometryData;var i,j,k,ii,numPts,numPolys,sourceTexCoords,x=[],tc=[],v1=[],v2=[],pts=[],posIndex=0,normIndex=0,colorIndex=0,texCoordIndex=0,positions=[],normals=[],colors=[],texCoords=[],indices=[],tristrip=null,sourcePositions=null,sourceColors=null;for(x.length=3,tc.length=2,v1.length=3,v2.length=3,pts.length=3,i=0;3>i;i+=1)v1[i]=m_point1[i]-m_origin[i],v2[i]=m_point2[i]-m_origin[i];for(numPts=(m_xresolution+1)*(m_yresolution+1),numPolys=m_xresolution*m_yresolution*2,positions.length=3*numPts,normals.length=3*numPts,texCoords.length=2*numPts,indices.length=numPts,k=0,i=0;m_yresolution+1>i;i+=1)for(tc[1]=i/m_yresolution,j=0;m_xresolution+1>j;j+=1){for(tc[0]=j/m_xresolution,ii=0;3>ii;ii+=1)x[ii]=m_origin[ii]+tc[0]*v1[ii]+tc[1]*v2[ii];positions[posIndex++]=x[0],positions[posIndex++]=x[1],positions[posIndex++]=x[2],colors[colorIndex++]=1,colors[colorIndex++]=1,colors[colorIndex++]=1,normals[normIndex++]=m_normal[0],normals[normIndex++]=m_normal[1],normals[normIndex++]=m_normal[2],texCoords[texCoordIndex++]=tc[0],texCoords[texCoordIndex++]=tc[1]}for(i=0;m_yresolution>i;i+=1)for(j=0;m_xresolution>j;j+=1)pts[0]=j+i*(m_xresolution+1),pts[1]=pts[0]+1,pts[2]=pts[0]+m_xresolution+2,pts[3]=pts[0]+m_xresolution+1;for(i=0;numPts>i;i+=1)indices[i]=i;return tristrip=new vgl.triangleStrip,tristrip.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(positions),sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(colors),sourceTexCoords=vgl.sourceDataT2fv(),sourceTexCoords.pushBack(texCoords),m_geom.addSource(sourcePositions),m_geom.addSource(sourceColors),m_geom.addSource(sourceTexCoords),m_geom.addPrimitive(tristrip),m_geom}},inherit(vgl.planeSource,vgl.source),vgl.pointSource=function(){"use strict";if(!(this instanceof vgl.pointSource))return new vgl.pointSource;vgl.source.call(this);var m_this=this,m_positions=[],m_colors=[],m_textureCoords=[],m_size=[],m_geom=null;this.getPositions=function(){return m_positions},this.setPositions=function(positions){positions instanceof Array?m_positions=positions:console.log("[ERROR] Invalid data type for positions. Array is required."),m_this.modified()},this.getColors=function(){return m_colors},this.setColors=function(colors){colors instanceof Array?m_colors=colors:console.log("[ERROR] Invalid data type for colors. Array is required."),m_this.modified()},this.getSize=function(){return m_size},this.setSize=function(size){m_size=size,this.modified()},this.setTextureCoordinates=function(texcoords){texcoords instanceof Array?m_textureCoords=texcoords:console.log("[ERROR] Invalid data type for texture coordinates. Array is required."),m_this.modified()},this.create=function(){if(m_geom=new vgl.geometryData,m_positions.length%3!==0)return void console.log("[ERROR] Invalid length of the points array");var pointsPrimitive,sourcePositions,sourceColors,sourceTexCoords,sourceSize,numPts=m_positions.length/3,i=0,indices=[];for(indices.length=numPts,i=0;numPts>i;i+=1)indices[i]=i;if(sourceSize=vgl.sourceDataDf(),numPts!==m_size.length)for(i=0;numPts>i;i+=1)sourceSize.pushBack(m_size);else sourceSize.setData(m_size);return m_geom.addSource(sourceSize),pointsPrimitive=new vgl.points,pointsPrimitive.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(m_positions),m_geom.addSource(sourcePositions),m_colors.length>0&&m_colors.length===m_positions.length?(sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(m_colors),m_geom.addSource(sourceColors)):m_colors.length>0&&m_colors.length!==m_positions.length&&console.log("[ERROR] Number of colors are different than number of points"),m_textureCoords.length>0&&m_textureCoords.length===m_positions.length?(sourceTexCoords=vgl.sourceDataT2fv(),sourceTexCoords.pushBack(m_textureCoords),m_geom.addSource(sourceTexCoords)):m_textureCoords.length>0&&m_textureCoords.length/2!==m_positions.length/3&&console.log("[ERROR] Number of texture coordinates are different than number of points"),m_geom.addPrimitive(pointsPrimitive),m_geom}},inherit(vgl.pointSource,vgl.source),vgl.lineSource=function(positions,colors){"use strict";if(!(this instanceof vgl.lineSource))return new vgl.lineSource;vgl.source.call(this);var m_positions=positions,m_colors=colors;this.setPositions=function(positions){return positions instanceof Array?(m_positions=positions,this.modified(),!0):(console.log("[ERROR] Invalid data type for positions. Array is required."),!1)},this.setColors=function(colors){return colors instanceof Array?(m_colors=colors,this.modified(),!0):(console.log("[ERROR] Invalid data type for colors. Array is required."),!1)},this.create=function(){if(!m_positions)return void console.log("[error] Invalid positions");if(m_positions.length%3!==0)return void console.log("[error] Line source requires 3d points");if(m_positions.length%3!==0)return void console.log("[ERROR] Invalid length of the points array");var i,linesPrimitive,sourcePositions,sourceColors,m_geom=new vgl.geometryData,numPts=m_positions.length/3,indices=[];for(indices.length=numPts,i=0;numPts>i;i+=1)indices[i]=i;return linesPrimitive=new vgl.lines,linesPrimitive.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(m_positions),m_geom.addSource(sourcePositions),m_colors&&m_colors.length>0&&m_colors.length===m_positions.length?(sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(m_colors),m_geom.addSource(sourceColors)):m_colors&&m_colors.length>0&&m_colors.length!==m_positions.length&&console.log("[error] Number of colors are different than number of points"),m_geom.addPrimitive(linesPrimitive),m_geom}},inherit(vgl.lineSource,vgl.source),vgl.utils=function(){"use strict";return this instanceof vgl.utils?(vgl.object.call(this),this):new vgl.utils},inherit(vgl.utils,vgl.object),vgl.utils.computePowerOfTwo=function(value,pow){"use strict";for(pow=pow||1;value>pow;)pow*=2;return pow},vgl.utils.createTextureVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 textureCoord;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iTextureCoord = textureCoord;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createTextureFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying highp vec3 iTextureCoord;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyz, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createRgbaTextureFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying highp vec3 iTextureCoord;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {"," mediump vec4 color = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyzw);"," color.w *= opacity;"," gl_FragColor = color;","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying mediump vec3 iVertexColor;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPointVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","attribute float vertexSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying mediump vec3 iVertexColor;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = vertexSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createVertexShaderSolidColor=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createVertexShaderColorMap=function(context,min,max){"use strict";context=context,min=min,max=max;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute float vertexScalar;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float lutMin;","uniform float lutMax;","varying mediump float iVertexScalar;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);","iVertexScalar = (vertexScalar-lutMin)/(lutMax-lutMin);","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump vec3 iVertexColor;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(iVertexColor, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createPhongVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute highp vec3 vertexPosition;","attribute mediump vec3 vertexNormal;","attribute mediump vec3 vertexColor;","uniform highp mat4 projectionMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 normalMatrix;","varying highp vec4 varPosition;","varying mediump vec3 varNormal;","varying mediump vec3 varVertexColor;","void main(void)","{","varPosition = modelViewMatrix * vec4(vertexPosition, 1.0);","gl_Position = projectionMatrix * varPosition;","varNormal = vec3(normalMatrix * vec4(vertexNormal, 0.0));","varVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPhongFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["uniform mediump float opacity;","precision mediump float;","varying vec3 varNormal;","varying vec4 varPosition;","varying mediump vec3 varVertexColor;","const vec3 lightPos = vec3(0.0, 0.0,10000.0);","const vec3 ambientColor = vec3(0.01, 0.01, 0.01);","const vec3 specColor = vec3(0.0, 0.0, 0.0);","void main() {","vec3 normal = normalize(varNormal);","vec3 lightDir = normalize(lightPos);","vec3 reflectDir = -reflect(lightDir, normal);","vec3 viewDir = normalize(-varPosition.xyz);","float lambertian = max(dot(lightDir, normal), 0.0);","vec3 color = vec3(0.0);","if(lambertian > 0.0) {"," color = lambertian * varVertexColor;","}","gl_FragColor = vec4(color * opacity, 1.0 - opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createFragmentShaderSolidColor=function(context,color){"use strict";var fragmentShaderSource=["uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4("+color[0]+","+color[1]+","+color[2]+", opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createFragmentShaderColorMap=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump float iVertexScalar;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(texture2D(sampler2d, vec2(iVertexScalar, 0.0)).xyz, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createPointSpritesVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","uniform mediump vec2 pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float height;","varying mediump vec3 iVertexColor;","varying highp float iVertexScalar;","void main(void)","{","mediump float realPointSize = pointSize.y;","if (pointSize.x > pointSize.y) {"," realPointSize = pointSize.x;}","gl_PointSize = realPointSize ;","iVertexScalar = vertexPosition.z;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition.xy, height, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPointSpritesFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump vec3 iVertexColor;","varying highp float iVertexScalar;","uniform sampler2D opacityLookup;","uniform highp float lutMin;","uniform highp float lutMax;","uniform sampler2D scalarsToColors;","uniform int useScalarsToColors;","uniform int useVertexColors;","uniform mediump vec2 pointSize;","uniform mediump float vertexColorWeight;","void main(void) {","mediump vec2 realTexCoord;","if (pointSize.x > pointSize.y) {"," realTexCoord = vec2(1.0, pointSize.y/pointSize.x) * gl_PointCoord;","} else {"," realTexCoord = vec2(pointSize.x/pointSize.y, 1.0) * gl_PointCoord;","}","highp float texOpacity = texture2D(opacityLookup, realTexCoord).w;","if (useScalarsToColors == 1) {"," gl_FragColor = vec4(texture2D(scalarsToColors, vec2((iVertexScalar - lutMin)/(lutMax - lutMin), 0.0)).xyz, texOpacity);","} else if (useVertexColors == 1) {"," gl_FragColor = vec4(iVertexColor, texOpacity);","} else {"," gl_FragColor = vec4(texture2D(opacityLookup, realTexCoord).xyz, texOpacity);","}}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createTextureMaterial=function(isRgba,origin){"use strict";var modelViewUniform,mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createTextureVertexShader(vgl.GL),fragmentShader=null,posVertAttr=new vgl.vertexAttribute("vertexPosition"),texCoordVertAttr=new vgl.vertexAttribute("textureCoord"),pointsizeUniform=new vgl.floatUniform("pointSize",5),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"sampler2d"),opacityUniform=null;return modelViewUniform=void 0!==origin?new vgl.modelViewOriginUniform("modelViewMatrix",origin):new vgl.modelViewUniform("modelViewMatrix"),samplerUniform.set(0),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(texCoordVertAttr,vgl.vertexAttributeKeys.TextureCoordinate),prog.addUniform(pointsizeUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),fragmentShader=isRgba?vgl.utils.createRgbaTextureFragmentShader(vgl.GL):vgl.utils.createTextureFragmentShader(vgl.GL),opacityUniform=new vgl.floatUniform("opacity",1),prog.addUniform(opacityUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createGeometryMaterial=function(){"use strict";var mat=new vgl.material,prog=new vgl.shaderProgram,pointSize=5,opacity=1,vertexShader=vgl.utils.createVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),pointsizeUniform=new vgl.floatUniform("pointSize",pointSize),opacityUniform=new vgl.floatUniform("opacity",opacity),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat},vgl.utils.createPointGeometryMaterial=function(opacity){"use strict";opacity=void 0===opacity?1:opacity;var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPointVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),sizeVertAttr=new vgl.vertexAttribute("vertexSize"),opacityUniform=new vgl.floatUniform("opacity",opacity),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addVertexAttribute(sizeVertAttr,vgl.vertexAttributeKeys.Scalar),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createPhongMaterial=function(){"use strict";var mat=new vgl.material,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPhongVertexShader(vgl.GL),fragmentShader=vgl.utils.createPhongFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),normalVertAttr=new vgl.vertexAttribute("vertexNormal"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),normalUniform=new vgl.normalMatrixUniform("normalMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(normalVertAttr,vgl.vertexAttributeKeys.Normal),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addUniform(normalUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat},vgl.utils.createColorMaterial=function(){"use strict";var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),texCoordVertAttr=new vgl.vertexAttribute("textureCoord"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addVertexAttribute(texCoordVertAttr,vgl.vertexAttributeKeys.TextureCoordinate),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createColorMappedMaterial=function(lut){"use strict";lut||(lut=new vgl.lookupTable);var scalarRange=lut.range(),mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShaderColorMap(vgl.GL,scalarRange[0],scalarRange[1]),fragmentShader=vgl.utils.createFragmentShaderColorMap(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),scalarVertAttr=new vgl.vertexAttribute("vertexScalar"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),lutMinUniform=new vgl.floatUniform("lutMin",scalarRange[0]),lutMaxUniform=new vgl.floatUniform("lutMax",scalarRange[1]),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.FLOAT,"sampler2d"),lookupTable=lut;return samplerUniform.set(0),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(scalarVertAttr,vgl.vertexAttributeKeys.Scalar),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(lutMinUniform),prog.addUniform(lutMaxUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat.addAttribute(lookupTable),mat},vgl.utils.updateColorMappedMaterial=function(mat,lut){"use strict";if(!mat)return void console.log("[warning] Invalid material. Nothing to update.");if(!lut)return void console.log("[warning] Invalid lookup table. Nothing to update.");var lutMin=mat.shaderProgram().uniform("lutMin"),lutMax=mat.shaderProgram().uniform("lutMax");lutMin.set(lut.range()[0]),lutMax.set(lut.range()[1]),mat.setAttribute(lut)},vgl.utils.createSolidColorMaterial=function(color){"use strict";color||(color=[1,1,1]);var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShaderSolidColor(vgl.GL),fragmentShader=vgl.utils.createFragmentShaderSolidColor(vgl.GL,color),posVertAttr=new vgl.vertexAttribute("vertexPosition"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createPointSpritesMaterial=function(image,lut){"use strict";var scalarRange=void 0===lut?[0,1]:lut.range(),mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPointSpritesVertexShader(vgl.GL),fragmentShader=vgl.utils.createPointSpritesFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),heightUniform=new vgl.floatUniform("height",0),vertexColorWeightUniform=new vgl.floatUniform("vertexColorWeight",0),lutMinUniform=new vgl.floatUniform("lutMin",scalarRange[0]),lutMaxUniform=new vgl.floatUniform("lutMax",scalarRange[1]),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"opacityLookup"),scalarsToColors=new vgl.uniform(vgl.GL.INT,"scalarsToColors"),useScalarsToColors=new vgl.uniform(vgl.GL.INT,"useScalarsToColors"),useVertexColors=new vgl.uniform(vgl.GL.INT,"useVertexColors"),pointSize=new vgl.uniform(vgl.GL.FLOAT_VEC2,"pointSize"),texture=new vgl.texture;return samplerUniform.set(0),scalarsToColors.set(1),useScalarsToColors.set(0),useVertexColors.set(0),pointSize.set([1,1]),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(heightUniform),prog.addUniform(vertexColorWeightUniform),
-prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addUniform(samplerUniform),prog.addUniform(useVertexColors),prog.addUniform(useScalarsToColors),prog.addUniform(pointSize),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),lut&&(prog.addUniform(scalarsToColors),useScalarsToColors.set(1),prog.addUniform(lutMinUniform),prog.addUniform(lutMaxUniform),lut.setTextureUnit(1),mat.addAttribute(lut)),texture.setImage(image),texture.setTextureUnit(0),mat.addAttribute(texture),mat},vgl.utils.createPlane=function(originX,originY,originZ,point1X,point1Y,point1Z,point2X,point2Y,point2Z){"use strict";var mapper=new vgl.mapper,planeSource=new vgl.planeSource,mat=vgl.utils.createGeometryMaterial(),actor=new vgl.actor;return planeSource.setOrigin(originX,originY,originZ),planeSource.setPoint1(point1X,point1Y,point1Z),planeSource.setPoint2(point2X,point2Y,point2Z),mapper.setGeometryData(planeSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createTexturePlane=function(originX,originY,originZ,point1X,point1Y,point1Z,point2X,point2Y,point2Z,isRgba){"use strict";var mapper=new vgl.mapper,planeSource=new vgl.planeSource,mat=vgl.utils.createTextureMaterial(isRgba,[originX,originY,originZ]),actor=new vgl.actor;return planeSource.setPoint1(point1X-originX,point1Y-originY,point1Z-originZ),planeSource.setPoint2(point2X-originX,point2Y-originY,point2Z-originZ),mapper.setGeometryData(planeSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createPoints=function(positions,size,colors,texcoords,opacity){"use strict";if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;opacity=void 0===opacity?1:opacity;var mapper=new vgl.mapper,pointSource=new vgl.pointSource,mat=vgl.utils.createPointGeometryMaterial(opacity),actor=new vgl.actor;return pointSource.setPositions(positions),colors&&pointSource.setColors(colors),texcoords&&pointSource.setTextureCoordinates(texcoords),size?pointSource.setSize(size):pointSource.setSize(1),mapper.setGeometryData(pointSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createPointSprites=function(image,positions,colors,texcoords){"use strict";if(!image)return console.log("[ERROR] Point sprites requires an image"),null;if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;var mapper=new vgl.mapper,pointSource=new vgl.pointSource,mat=vgl.utils.createPointSpritesMaterial(image),actor=new vgl.actor;return pointSource.setPositions(positions),colors&&pointSource.setColors(colors),texcoords&&pointSource.setTextureCoordinates(texcoords),mapper.setGeometryData(pointSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createLines=function(positions,colors){"use strict";if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;var mapper=new vgl.mapper,lineSource=new vgl.lineSource,mat=vgl.utils.createGeometryMaterial(),actor=new vgl.actor;return lineSource.setPositions(positions),colors&&lineSource.setColors(colors),mapper.setGeometryData(lineSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createColorLegend=function(varname,lookupTable,origin,width,height,countMajor,countMinor){"use strict";function createLabels(varname,positions,range){if(!positions)return void console.log("[error] Create labels requires positions (x,y,z) array");if(positions.length%3!==0)return void console.log("[error] Create labels require positions array contain 3d points");if(!range)return void console.log("[error] Create labels requires Valid range");var i,actor=null,size=vgl.utils.computePowerOfTwo(48),index=0,actors=[],origin=[],pt1=[],pt2=[],delta=positions[6]-positions[0],axisLabelOffset=4;for(origin.length=3,pt1.length=3,pt2.length=3,i=0;2>i;i+=1)index=i*(positions.length-3),origin[0]=positions[index]-delta,origin[1]=positions[index+1]-2*delta,origin[2]=positions[index+2],pt1[0]=positions[index]+delta,pt1[1]=origin[1],pt1[2]=origin[2],pt2[0]=origin[0],pt2[1]=positions[1],pt2[2]=origin[2],actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1[0],pt1[1],pt1[2],pt2[0],pt2[1],pt2[2],!0),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.material().addAttribute(vgl.utils.create2DTexture(range[i].toFixed(2).toString(),12,null)),actors.push(actor);return origin[0]=.5*(positions[0]+positions[positions.length-3]-size),origin[1]=positions[1]+axisLabelOffset,origin[2]=positions[2],pt1[0]=origin[0]+size,pt1[1]=origin[1],pt1[2]=origin[2],pt2[0]=origin[0],pt2[1]=origin[1]+size,pt2[2]=origin[2],actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1[0],pt1[1],pt1[2],pt2[0],pt2[1],pt2[2],!0),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.material().addAttribute(vgl.utils.create2DTexture(varname,24,null)),actors.push(actor),actors}function createTicksAndLabels(varname,lut,originX,originY,originZ,pt1X,pt1Y,pt1Z,pt2X,pt2Y,pt2Z,countMajor,countMinor,heightMajor,heightMinor){heightMinor=heightMinor;var width=pt2X-pt1X,index=null,delta=width/countMajor,positions=[],actors=[];for(index=0;countMajor>=index;index+=1)positions.push(pt1X+delta*index),positions.push(pt1Y),positions.push(pt1Z),positions.push(pt1X+delta*index),positions.push(pt1Y+heightMajor),positions.push(pt1Z);return actors=actors.concat(createLabels(varname,positions,lut.range()))}if(!lookupTable)return console.log("[error] Invalid lookup table"),[];var pt1X=origin[0]+width,pt1Y=origin[1],pt1Z=0,pt2X=origin[0],pt2Y=origin[1]+height,pt2Z=0,actors=[],actor=null,mat=null,group=vgl.groupNode();return actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1X,pt1Y,pt1Z,pt2X,pt2Y,pt2Z,!0),mat=actor.material(),mat.addAttribute(lookupTable),actor.setMaterial(mat),group.addChild(actor),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actors.push(actor),actors=actors.concat(createTicksAndLabels(varname,lookupTable,origin[0],origin[1],origin[1],pt2X,pt1Y,pt1Z,pt1X,pt1Y,pt1Z,countMajor,countMinor,5,3))},vgl.utils.create2DTexture=function(textToWrite,textSize,color,font,alignment,baseline,bold){"use strict";var canvas=document.getElementById("textRendering"),ctx=null,texture=vgl.texture();return font=font||"sans-serif",alignment=alignment||"center",baseline=baseline||"bottom","undefined"==typeof bold&&(bold=!0),canvas||(canvas=document.createElement("canvas")),ctx=canvas.getContext("2d"),canvas.setAttribute("id","textRendering"),canvas.style.display="none",canvas.height=vgl.utils.computePowerOfTwo(8*textSize),canvas.width=canvas.height,ctx.fillStyle="rgba(0, 0, 0, 0)",ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height),ctx.fillStyle="rgba(200, 85, 10, 1.0)",ctx.textAlign=alignment,ctx.textBaseline=baseline,ctx.font=4*textSize+"px "+font,bold&&(ctx.font="bold "+ctx.font),ctx.fillText(textToWrite,canvas.width/2,canvas.height/2,canvas.width),texture.setImage(canvas),texture.updateDimensions(),texture},vgl.picker=function(){"use strict";if(!(this instanceof vgl.picker))return new vgl.picker;vgl.object.call(this);var m_actors=[];return this.getActors=function(){return m_actors},this.pick=function(selectionX,selectionY,renderer){if(void 0===selectionX)return 0;if(void 0===selectionY)return 0;if(void 0===renderer)return 0;m_actors=[];var actors,count,i,bb,tmin,tmax,tymin,tymax,tzmin,tzmax,actor,camera=renderer.camera(),width=renderer.width(),height=renderer.height(),fpoint=camera.focalPoint(),focusWorldPt=vec4.fromValues(fpoint[0],fpoint[1],fpoint[2],1),focusDisplayPt=renderer.worldToDisplay(focusWorldPt,camera.viewMatrix(),camera.projectionMatrix(),width,height),displayPt=vec4.fromValues(selectionX,selectionY,focusDisplayPt[2],1),worldPt=renderer.displayToWorld(displayPt,camera.viewMatrix(),camera.projectionMatrix(),width,height),cameraPos=camera.position(),ray=[];for(i=0;3>i;i+=1)ray[i]=worldPt[i]-cameraPos[i];for(actors=renderer.sceneRoot().children(),count=0,i=0;i=0?(tmin=(bb[0]-cameraPos[0])/ray[0],tmax=(bb[1]-cameraPos[0])/ray[0]):(tmin=(bb[1]-cameraPos[0])/ray[0],tmax=(bb[0]-cameraPos[0])/ray[0]),ray[1]>=0?(tymin=(bb[2]-cameraPos[1])/ray[1],tymax=(bb[3]-cameraPos[1])/ray[1]):(tymin=(bb[3]-cameraPos[1])/ray[1],tymax=(bb[2]-cameraPos[1])/ray[1]),tmin>tymax||tymin>tmax)continue;if(tymin>tmin&&(tmin=tymin),tmax>tymax&&(tmax=tymax),ray[2]>=0?(tzmin=(bb[4]-cameraPos[2])/ray[2],tzmax=(bb[5]-cameraPos[2])/ray[2]):(tzmin=(bb[5]-cameraPos[2])/ray[2],tzmax=(bb[4]-cameraPos[2])/ray[2]),tmin>tzmax||tzmin>tmax)continue;tzmin>tmin&&(tmin=tzmin),tmax>tzmax&&(tmax=tzmax),m_actors[count]=actor,count+=1}return count},this},inherit(vgl.picker,vgl.object),vgl.shapefileReader=function(){"use strict";if(!(this instanceof vgl.shapefileReader))return new vgl.shapefileReader;var m_that=this,SHP_NULL=0,SHP_POINT=1,SHP_POLYGON=5,SHP_POLYLINE=3;return this.int8=function(data,offset){return data.charCodeAt(offset)},this.bint32=function(data,offset){return((255&data.charCodeAt(offset))<<24)+((255&data.charCodeAt(offset+1))<<16)+((255&data.charCodeAt(offset+2))<<8)+(255&data.charCodeAt(offset+3))},this.lint32=function(data,offset){return((255&data.charCodeAt(offset+3))<<24)+((255&data.charCodeAt(offset+2))<<16)+((255&data.charCodeAt(offset+1))<<8)+(255&data.charCodeAt(offset))},this.bint16=function(data,offset){return((255&data.charCodeAt(offset))<<8)+(255&data.charCodeAt(offset+1))},this.lint16=function(data,offset){return((255&data.charCodeAt(offset+1))<<8)+(255&data.charCodeAt(offset))},this.ldbl64=function(data,offset){var b0=255&data.charCodeAt(offset),b1=255&data.charCodeAt(offset+1),b2=255&data.charCodeAt(offset+2),b3=255&data.charCodeAt(offset+3),b4=255&data.charCodeAt(offset+4),b5=255&data.charCodeAt(offset+5),b6=255&data.charCodeAt(offset+6),b7=255&data.charCodeAt(offset+7),sign=1-2*(b7>>7),exp=((127&b7)<<4)+((240&b6)>>4)-1023,frac=(15&b6)*Math.pow(2,48)+b5*Math.pow(2,40)+b4*Math.pow(2,32)+b3*Math.pow(2,24)+b2*Math.pow(2,16)+b1*Math.pow(2,8)+b0;return sign*(1+frac*Math.pow(2,-52))*Math.pow(2,exp)},this.lfloat32=function(data,offset){var b0=255&data.charCodeAt(offset),b1=255&data.charCodeAt(offset+1),b2=255&data.charCodeAt(offset+2),b3=255&data.charCodeAt(offset+3),sign=1-2*(b3>>7),exp=((127&b3)<<1)+((254&b2)>>7)-127,frac=(127&b2)*Math.pow(2,16)+b1*Math.pow(2,8)+b0;return sign*(1+frac*Math.pow(2,-23))*Math.pow(2,exp)},this.str=function(data,offset,length){for(var chars=[],index=offset;offset+length>index;){var c=data[index];if(0===c.charCodeAt(0))break;chars.push(c),index+=1}return chars.join("")},this.readHeader=function(data){var code=this.bint32(data,0),length=this.bint32(data,24),version=this.lint32(data,28),shapetype=this.lint32(data,32);return{code:code,length:length,version:version,shapetype:shapetype}},this.loadShx=function(data){for(var indices=[],appendIndex=function(offset){return indices.push(2*m_that.bint32(data,offset)),offset+8},offset=100;offsetheader_offset;)headers.push(readHeader(header_offset)),header_offset+=HEADER_LENGTH;for(var records=[],record_offset=header_size;header_size+num_entries*record_size>record_offset;){var declare=m_that.str(data,record_offset,1);if("*"===declare)record_offset+=record_size;else{record_offset+=1;for(var record={},i=0;i=start;i-=1){var x=m_that.ldbl64(data,offset+16*i),y=m_that.ldbl64(data,offset+16*i+8);ring.push([x,y])}return ring},readRecord=function(offset){var num_parts,num_points,parts_start,points_start,i,start,end,ring,rings,record_offset=offset+8,geom_type=m_that.lint32(data,record_offset);if(geom_type===SHP_NULL)console.log("NULL Shape");else if(geom_type===SHP_POINT){var x=m_that.ldbl64(data,record_offset+4),y=m_that.ldbl64(data,record_offset+12);features.push({type:"Point",attr:{},geom:[[x,y]]})}else if(geom_type===SHP_POLYGON){for(num_parts=m_that.lint32(data,record_offset+36),num_points=m_that.lint32(data,record_offset+40),parts_start=offset+52,points_start=offset+52+4*num_parts,rings=[],i=0;num_parts>i;i+=1)start=m_that.lint32(data,parts_start+4*i),end=num_parts>i+1?m_that.lint32(data,parts_start+4*(i+1)):num_points,ring=readRing(points_start,start,end),rings.push(ring);features.push({type:"Polygon",attr:{},geom:[rings]})}else{if(geom_type!==SHP_POLYLINE)throw"Not Implemented: "+geom_type;for(num_parts=m_that.lint32(data,record_offset+36),num_points=m_that.lint32(data,record_offset+40),parts_start=offset+52,points_start=offset+52+4*num_parts,rings=[],i=0;num_parts>i;i+=1)start=m_that.lint32(data,parts_start+4*i),end=num_parts>i+1?m_that.lint32(data,parts_start+4*(i+1)):num_points,ring=readRing(points_start,start,end),rings.push(ring);features.push({type:"Polyline",attr:{},geom:[rings]})}},attr=this.loadDBF(dbf_data);for(i=0;i=m_base64Str.length)return END_OF_INPUT;if(nextCharacter=m_base64Str.charAt(m_base64Count),m_base64Count+=1,m_reverseBase64Chars[nextCharacter])return m_reverseBase64Chars[nextCharacter];if("A"===nextCharacter)return 0}return END_OF_INPUT},this.decode64=function(str){var result="",inBuffer=new Array(4),done=!1;for(m_base64Str=str,m_base64Count=0;!done&&(inBuffer[0]=this.readReverseBase64())!==END_OF_INPUT&&(inBuffer[1]=this.readReverseBase64())!==END_OF_INPUT;)inBuffer[2]=this.readReverseBase64(),inBuffer[3]=this.readReverseBase64(),result+=this.ntos(inBuffer[0]<<2&255|inBuffer[1]>>4),inBuffer[2]!==END_OF_INPUT?(result+=this.ntos(inBuffer[1]<<4&255|inBuffer[2]>>2),inBuffer[3]!==END_OF_INPUT?result+=this.ntos(inBuffer[2]<<6&255|inBuffer[3]):done=!0):done=!0;return result},this.readNumber=function(ss){var v=ss[m_pos++]+(ss[m_pos++]<<8)+(ss[m_pos++]<<16)+(ss[m_pos++]<<24);return v},this.readF3Array=function(numberOfPoints,ss){var i,size=4*numberOfPoints*3,test=new Int8Array(size),points=null;for(i=0;size>i;i+=1)test[i]=ss[m_pos],m_pos+=1;return points=new Float32Array(test.buffer)},this.readColorArray=function(numberOfPoints,ss,vglcolors){var i,idx=0,tmp=new Array(3*numberOfPoints);for(i=0;numberOfPoints>i;i+=1)tmp[idx++]=ss[m_pos++]/255,tmp[idx++]=ss[m_pos++]/255,tmp[idx++]=ss[m_pos++]/255,m_pos++;vglcolors.insert(tmp)},this.parseObject=function(vtkObject){var size,actor,colorMapData,shaderProg,opacityUniform,lookupTable,colorTable,windowSize,width,height,position,geom=new vgl.geometryData,mapper=vgl.mapper(),ss=[],type=null,data=null,matrix=null,material=null;for(data=atob(vtkObject.data),i=0;ii;i+=1)p[idx++]=points[3*i],p[idx++]=points[3*i+1],p[idx++]=points[3*i+2];for(vglpoints.insert(p),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),vgllines=new vgl.lines,geom.addPrimitive(vgllines),numberOfIndex=this.readNumber(ss),temp=new Int8Array(2*numberOfIndex),i=0;2*numberOfIndex>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;for(index=new Uint16Array(temp.buffer),vgllines.setIndices(index),vgllines.setPrimitiveType(vgl.GL.LINES),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),matrix},this.parseMeshData=function(geom,ss){var numberOfIndex,numberOfPoints,points,temp,index,size,m,i,tcoord,vglpoints=null,vglcolors=null,normals=null,matrix=mat4.create(),vgltriangles=null,pn=null,idx=0;for(numberOfPoints=this.readNumber(ss),pn=new Array(6*numberOfPoints),vglpoints=new vgl.sourceDataP3N3f,points=this.readF3Array(numberOfPoints,ss),normals=this.readF3Array(numberOfPoints,ss),i=0;numberOfPoints>i;i+=1)pn[idx++]=points[3*i],pn[idx++]=points[3*i+1],pn[idx++]=points[3*i+2],pn[idx++]=normals[3*i],pn[idx++]=normals[3*i+1],pn[idx++]=normals[3*i+2];for(vglpoints.insert(pn),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),temp=[],vgltriangles=new vgl.triangles,numberOfIndex=this.readNumber(ss),temp=new Int8Array(2*numberOfIndex),i=0;2*numberOfIndex>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;for(index=new Uint16Array(temp.buffer),vgltriangles.setIndices(index),geom.addPrimitive(vgltriangles),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),tcoord=null,matrix},this.parsePointData=function(geom,ss){var numberOfPoints,points,indices,temp,size,m,matrix=mat4.create(),vglpoints=null,vglcolors=null,vglVertexes=null,p=null,idx=0;for(numberOfPoints=this.readNumber(ss),p=new Array(3*numberOfPoints),vglpoints=new vgl.sourceDataP3fv,points=this.readF3Array(numberOfPoints,ss),indices=new Uint16Array(numberOfPoints),i=0;numberOfPoints>i;i+=1)indices[i]=i,p[idx++]=points[3*i],p[idx++]=points[3*i+1],p[idx++]=points[3*i+2];for(vglpoints.insert(p),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),vglVertexes=new vgl.points,vglVertexes.setIndices(indices),geom.addPrimitive(vglVertexes),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),matrix},this.parseColorMapData=function(geom,ss,numColors){var tmpArray,size,xrgb,i,c,obj={};for(obj.numOfColors=numColors,size=8,tmpArray=new Int8Array(size),i=0;size>i;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;for(obj.position=new Float32Array(tmpArray.buffer),size=8,tmpArray=new Int8Array(size),i=0;size>i;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;for(obj.size=new Float32Array(tmpArray.buffer),obj.colors=[],c=0;ci;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;xrgb=[new Float32Array(tmpArray.buffer)[0],ss[m_pos++],ss[m_pos++],ss[m_pos++]],obj.colors[c]=xrgb}for(obj.orientation=ss[m_pos++],obj.numOfLabels=ss[m_pos++],obj.title="";m_pos=0;layer-=1)renderer=this.getRenderer(layer),this.parseSceneMetadata(renderer,layer);return m_viewer},this.createViewer=function(node){var interactorStyle;return null===m_viewer&&(m_node=node,m_viewer=vgl.viewer(node),m_viewer.init(),m_viewer.renderWindow().removeRenderer(m_viewer.renderWindow().activeRenderer()),m_viewer.renderWindow().addRenderer(new vgl.depthPeelRenderer),m_vtkRenderedList[0]=m_viewer.renderWindow().activeRenderer(),m_viewer.renderWindow().resize(node.width,node.height),interactorStyle=vgl.pvwInteractorStyle(),m_viewer.setInteractorStyle(interactorStyle)),m_viewer},this.deleteViewer=function(){m_vtkRenderedList={},m_viewer=null},this.updateCanvas=function(node){return m_node=node,m_viewer.renderWindow().resize(node.width,node.height),m_viewer},this.numObjects=function(){return m_vtkObjectCount},this.getRenderer=function(layer){var renderer;return renderer=m_vtkRenderedList[layer],(null===renderer||"undefined"==typeof renderer)&&(renderer=new vgl.renderer,renderer.setResetScene(!1),renderer.setResetClippingRange(!1),m_viewer.renderWindow().addRenderer(renderer),0!==layer&&renderer.camera().setClearMask(vgl.GL.DepthBufferBit),m_vtkRenderedList[layer]=renderer),renderer},this.setVtkScene=function(scene){m_vtkScene=scene},this},vgl.DataBuffers=function(initialSize){"use strict";if(!(this instanceof vgl.DataBuffers))return new vgl.DataBuffers(initialSize);var size,data={};size=initialSize||0===initialSize?initialSize:256;var current=0,copyArray=function(dst,src,start,count){dst||console.log("ack"),start||(start=0),count||(count=src.length);for(var i=0;count>i;i+=1)dst[start+i]=src[i]},resize=function(min_expand){var new_size=size;for(min_expand>2*new_size&&(new_size=min_expand);min_expand>new_size;)new_size*=2;size=new_size;for(var name in data)if(data.hasOwnProperty(name)){var newArray=new Float32Array(new_size*data[name].len),oldArray=data[name].array;copyArray(newArray,oldArray),data[name].array=newArray,data[name].dirty=!0}};this.create=function(name,len){if(!len)throw"Length of buffer must be a positive integer";var array=new Float32Array(size*len);return data[name]={array:array,len:len,dirty:!1},data[name].array},this.alloc=function(num){current+num>=size&&resize(current+num);var start=current;return current+=num,start},this.get=function(name){return data[name].array},this.write=function(name,array,start,count){copyArray(data[name].array,array,start*data[name].len,count*data[name].len),data[name].dirty=!0},this.repeat=function(name,elem,start,count){for(var i=0;count>i;i+=1)copyArray(data[name].array,elem,(start+i)*data[name].len,data[name].len);data[name].dirty=!0},this.count=function(){return current},this.data=function(name){return data[name].array}},function(){"use strict";function setNumeric(){var i;for(i=0;in?!1:(outer.forEach(function(vert,i){var j=(n+i-1)%n,intersect=outer[i].y>point.y!=outer[j].y>point.y&&point.x<(outer[j].x-outer[i].x)*(point.y-outer[i].y)/(outer[j].y-outer[i].y)+outer[i].x;intersect&&(inside=!inside)}),(inner||[]).forEach(function(hole){inside=inside&&!geo.util.pointInPolygon(point,hole)}),inside)},isFunction:function(f){return"function"==typeof f},ensureFunction:function(f){return geo.util.isFunction(f)?f:function(){return f}},randomString:function(n){var s,i,r;for(n=n||8,s="",i=0;n>i;i+=1)r=Math.floor(Math.random()*chars.length),s+=chars.substring(r,r+1);return s},convertColor:function(color){return void 0!==color.r&&void 0!==color.g&&void 0!==color.b?color:("string"==typeof color&&(geo.util.cssColors.hasOwnProperty(color)?color=geo.util.cssColors[color]:"#"===color.charAt(0)&&(color=parseInt(color.slice(1),16))),isFinite(color)&&(color={r:((16711680&color)>>16)/255,g:((65280&color)>>8)/255,b:(255&color)/255}),color)},normalizeCoordinates:function(p){return p=p||{},Array.isArray(p)?{x:p[0],y:p[1],z:p[2]||0}:{x:setNumeric(p.x,p.longitude,p.lng,p.lon,0),y:setNumeric(p.y,p.latitude,p.lat,0),z:setNumeric(p.z,p.elevation,p.elev,p.height,0)}}},geo.util.cssColors={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074}}(),function(){"use strict";function vect(x,y){return new Vector2D(x,y)}var RangeNode=function(elem,start,end,current){this.data=elem[current],this.left=null,this.right=null,start!==current&&(this.left=new RangeNode(elem,start,current-1,parseInt((start+(current-1))/2,10))),end!==current&&(this.right=new RangeNode(elem,current+1,end,parseInt((end+(current+1))/2,10))),this.elem=elem,this.start=start,this.end=end,this.subtree=null,this.search=rangeNodeSearch},rangeNodeSearch=function(result,box){var m_this=this,xrange=function(b){return b.x_in(m_this.elem[m_this.start])&&b.x_in(m_this.elem[m_this.end])},yrange=function(b,start,end){return b.y_in(m_this.subtree[start])&&b.y_in(m_this.subtree[end])},subquery=function(result,box,start,end,current){if(yrange(box,start,end))for(var i=start;end>=i;i++)result.push(m_this.subtree[i]);else box.y_in(m_this.subtree[current])&&result.push(m_this.subtree[current]),box.y_left(m_this.subtree[current])?current!==end&&subquery(result,box,current+1,end,parseInt((end+(current+1))/2,10)):box.x_right(m_this.subtree[current])?current!==start&&subquery(result,box,start,current-1,parseInt((start+(current-1))/2,10)):(current!==end&&subquery(result,box,current+1,end,parseInt((end+(current+1))/2,10)),
-current!==start&&subquery(result,box,start,current-1,parseInt((start+(current-1))/2,10)))};return xrange(box)?(this.subtree||(this.subtree=this.elem.slice(this.start,this.end+1),this.subtree.sort(function(a,b){return a.y-b.y})),void subquery(result,box,0,this.subtree.length-1,parseInt((this.subtree.length-1)/2,10))):(box.contains(this.data)&&result.push(this.data),void(box.x_left(this.data)?this.right&&this.right.search(result,box):box.x_right(this.data)?this.left&&this.left.search(result,box):(this.left&&this.left.search(result,box),this.right&&this.right.search(result,box))))},RangeTree=function(elem){elem.sort(function(a,b){return a.x-b.x}),elem.length>0?this.root=new RangeNode(elem,0,elem.length-1,parseInt((elem.length-1)/2,10)):this.root=null,this.search=function(_box){if(!this.root)return[];var box=_box.clone(),result=[];return this.root.search(result,box),result}},Box=function(v1,v2){this.min=v1.clone(),this.max=v2.clone(),this.contains=function(p){return v1.x<=p.x&&v2.x>=p.x&&v1.y<=p.y&&v2.y>=p.y},this.x_in=function(p){return v1.x<=p.x&&v2.x>=p.x},this.x_left=function(p){return v1.x>=p.x},this.x_right=function(p){return v2.x<=p.x},this.y_in=function(p){return v1.y<=p.y&&v2.y>=p.y},this.y_left=function(p){return v1.y>=p.y},this.y_right=function(p){return v2.y<=p.y},this.area=function(){return(this.max.x-this.min.x)*(this.max.y-this.min.y)},this.height=function(){return this.max.y-this.min.y},this.width=function(){return this.max.x-this.min.x},this.vertex=function(index){switch(index){case 0:return this.min.clone();case 1:return new vect(this.max.x,this.min.y);case 2:return this.max.clone();case 3:return new vect(this.min.x,this.max.y);default:throw"Index out of bounds: "+index}},this.intersects=function(box){for(var i=0;4>i;i++)for(var j=0;4>j;j++)if(vect.intersects(this.vertex(i),this.vertex((i+1)%4),box.vertex(j),box.vertex((j+1)%4)))return!0;return this.contains(box.min)&&this.contains(box.max)&&this.contains(new vect(box.min.x,box.max.y))&&this.contains(new vect(box.max.x,box.min.y))?!0:box.contains(this.min)&&box.contains(this.max)&&box.contains(new vect(this.min.x,this.max.y))&&box.contains(new vect(this.max.x,this.min.y))?!0:!1},this.union=function(b){this.min.x=Math.min(this.min.x,b.min.x),this.min.y=Math.min(this.min.y,b.min.y),this.max.x=Math.max(this.max.x,b.max.x),this.max.y=Math.max(this.max.y,b.max.y)},this.centroid=function(){return new vect((this.max.x+this.min.x)/2,(this.max.y+this.min.y)/2)},this.clone=function(){return new Box(v1,v2)}},Vector2D=function(x,y){this.x=x,this.y=y,this.add=function(v){return this.x+=v.x,this.y+=v.y,this},this.sub=function(v){return this.x-=v.x,this.y-=v.y,this},this.scale=function(s){return this.x*=s,this.y*=s,this},this.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},this.normalize=function(){var scale=this.length();return 0===scale?this:(this.x/=scale,this.y/=scale,this)},this.div=function(v){return this.x/=v.x,this.y/=v.y,this},this.floor=function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},this.zero=function(tol){return tol=tol||0,this.length()<=tol},this.dot=function(v){return this.x*v.x+this.y*v.y},this.cross=function(v){return this.x*v.y-this.y*v.x},this.rotate=function(omega){var cos=Math.cos(omega),sin=Math.sin(omega);return xp=cos*this.x-sin*this.y,yp=sin*this.x+cos*this.y,this.x=xp,this.y=yp,this},this.clone=function(){return new Vector2D(this.x,this.y)},this.array=function(){return[this.x,this.y]}};vect.scale=function(v,s){return v.clone().scale(s)},vect.add=function(v1,v2){return v1.clone().add(v2)},vect.sub=function(v1,v2){return v1.clone().sub(v2)},vect.dist=function(v1,v2){return v1.clone().sub(v2).length()},vect.dir=function(v1,v2){return v1.clone().sub(v2).normalize()},vect.dot=function(v1,v2){return v1.x*v2.x+v1.y*v2.y},vect.cross=function(v1,v2){return v1.x*v2.y-v1.y*v2.x},vect.left=function(a,b,c,tol){tol||(tol=0);var v1=vect.sub(b,a),v2=vect.sub(c,a);return vect.cross(v1,v2)>=-tol},vect.intersects=function(a,b,c,d,tol){return tol||(tol=0),vect.left(a,b,c,tol)!=vect.left(a,b,d,tol)&&vect.left(c,d,b,tol)!=vect.left(c,d,a,tol)},vect.intersect2dt=function(a,b,c,d){var denom=a.x*(d.y-c.y)+b.x*(c.y-d.y)+d.x*(b.y-a.y)+c.x*(a.y-b.y);if(0===denom)return 1/0;var num_t=(a.x*(d.y-c.y)+c.x*(a.y-d.y)+d.x*(c.y-a.y),-(a.x*(c.y-b.y)+b.x*(a.y-c.y)+c.x*(b.y-a.y))),t=num_t/denom;return t},vect.intersect2dpos=function(a,b,c,d){var denom=a.x*(d.y-c.y)+b.x*(c.y-d.y)+d.x*(b.y-a.y)+c.x*(a.y-b.y);if(0===denom)return 1/0;var num_s=a.x*(d.y-c.y)+c.x*(a.y-d.y)+d.x*(c.y-a.y),s=num_s/denom,dir=vect.sub(b,a);return dir.scale(s),vect.add(a,dir)},vect.rotate=function(v,omega){var cos=Math.cos(omega),sin=Math.sin(omega);xp=cos*v.x-sin*v.y,yp=sin*v.x+cos*v.y;var c=new vect(xp,yp);return c},vect.normalize=function(v){return v.clone().normalize()},geo.util.RangeTree=RangeTree,geo.util.Box=Box,geo.util.vect=vect}(),function(){"use strict";var L={};L.Util={stamp:function(obj){return obj._leaflet_id=obj._leaflet_id||++L.Util.lastId,obj._leaflet_id},lastId:0},geo.util.DistanceGrid=function(cellSize){this._cellSize=cellSize,this._sqCellSize=cellSize*cellSize,this._grid={},this._objectPoint={}},geo.util.DistanceGrid.prototype={addObject:function(obj,point){var x=this._getCoord(point.x),y=this._getCoord(point.y),grid=this._grid,row=grid[y]=grid[y]||{},cell=row[x]=row[x]||[],stamp=L.Util.stamp(obj);point.obj=obj,this._objectPoint[stamp]=point,cell.push(obj)},updateObject:function(obj,point){this.removeObject(obj),this.addObject(obj,point)},removeObject:function(obj,point){var i,len,x=this._getCoord(point.x),y=this._getCoord(point.y),grid=this._grid,row=grid[y]=grid[y]||{},cell=row[x]=row[x]||[];for(delete this._objectPoint[L.Util.stamp(obj)],i=0,len=cell.length;len>i;i++)if(cell[i]===obj)return cell.splice(i,1),1===len&&delete row[x],!0},eachObject:function(fn,context){var i,j,k,len,row,cell,removed,grid=this._grid;for(i in grid){row=grid[i];for(j in row)for(cell=row[j],k=0,len=cell.length;len>k;k++)removed=fn.call(context,cell[k]),removed&&(k--,len--)}},getNearObject:function(point){var i,j,k,row,cell,len,obj,dist,x=this._getCoord(point.x),y=this._getCoord(point.y),objectPoint=this._objectPoint,closestDistSq=this._sqCellSize,closest=null;for(i=y-1;y+1>=i;i++)if(row=this._grid[i])for(j=x-1;x+1>=j;j++)if(cell=row[j])for(k=0,len=cell.length;len>k;k++)obj=cell[k],dist=this._sqDist(objectPoint[L.Util.stamp(obj)],point),closestDistSq>dist&&(closestDistSq=dist,closest=obj);return closest},contents:function(){return $.map(this._objectPoint,function(val){return val})},_getCoord:function(x){return Math.floor(x/this._cellSize)},_sqDist:function(p,p2){var dx=p2.x-p.x,dy=p2.y-p.y;return dx*dx+dy*dy}}}(),function(){"use strict";function ClusterTree(group,zoom,children){this._group=group,this._zoom=zoom,this._points=[],this._clusters=[],this._count=0,this._parent=null,this._coord=null;var that=this;(children||[]).forEach(function(c){that._add(c)})}function C(opts,width,height){this._opts=$.extend({maxZoom:18,radius:.05},opts),this._opts.width=this._opts.width||width||256,this._opts.height=this._opts.height||height||256,this._clusters={},this._points={};var zoom,scl;for(zoom=this._opts.maxZoom;zoom>=0;zoom-=1)scl=this._scaleAtLevel(zoom,this._opts.width,this._opts.height),this._clusters[zoom]=new geo.util.DistanceGrid(scl),this._points[zoom]=new geo.util.DistanceGrid(scl);this._topClusterLevel=new ClusterTree(this,-1)}ClusterTree.prototype._add=function(pt){var inc=1;pt instanceof ClusterTree?(this._clusters.push(pt),inc=pt._count):this._points.push(pt),pt._parent=this,this._increment(inc)},ClusterTree.prototype._increment=function(inc){this._coord=null,this._count+=inc,this._parent&&this._parent._increment(inc)},ClusterTree.prototype.count=function(){return this._count},ClusterTree.prototype.each=function(func){var i;for(i=0;i=0;zoom-=1){if(closest=this._clusters[zoom].getNearObject(point))return void closest._add(point);if(closest=this._points[zoom].getNearObject(point)){if(parent=closest._parent)for(z=parent._points.length-1;z>=0;z-=1)if(parent._points[z]===closest){parent._points.splice(z,1),parent._increment(-1);break}for(parent||$.noop(),newCluster=new ClusterTree(this,zoom,[closest,point]),this._clusters[zoom].addObject(newCluster,newCluster.coords()),lastParent=newCluster,z=zoom-1;z>parent._zoom;z-=1)lastParent=new ClusterTree(this,z,[lastParent]),this._clusters[z].addObject(lastParent,lastParent.coords());for(parent._add(lastParent),z=zoom;z>=0&&this._points[z].removeObject(closest,closest);z-=1);return}this._points[zoom].addObject(point,point)}this._topClusterLevel._add(point)},C.prototype.points=function(zoom){return zoom=Math.min(Math.max(Math.floor(zoom),0),this._opts.maxZoom-1),this._points[Math.floor(zoom)].contents()},C.prototype.clusters=function(zoom){return zoom=Math.min(Math.max(Math.floor(zoom),0),this._opts.maxZoom-1),this._clusters[Math.floor(zoom)].contents()},geo.util.ClusterGroup=C}(),geo.object=function(){"use strict";if(!(this instanceof geo.object))return new geo.object;var m_this=this,m_eventHandlers={},m_idleHandlers=[],m_deferredCount=0;return this.onIdle=function(handler){return m_deferredCount?m_idleHandlers.push(handler):handler(),m_this},this.addDeferred=function(defer){return m_deferredCount+=1,defer.done(function(){m_deferredCount-=1,m_deferredCount||m_idleHandlers.splice(0,m_idleHandlers.length).forEach(function(handler){handler()})}),m_this},this.geoOn=function(event,handler){return Array.isArray(event)?(event.forEach(function(e){m_this.geoOn(e,handler)}),m_this):(m_eventHandlers.hasOwnProperty(event)||(m_eventHandlers[event]=[]),m_eventHandlers[event].push(handler),m_this)},this.geoTrigger=function(event,args){return Array.isArray(event)?(event.forEach(function(e){m_this.geoTrigger(e,args)}),m_this):(m_eventHandlers.hasOwnProperty(event)&&m_eventHandlers[event].forEach(function(handler){handler.call(m_this,args)}),m_this)},this.geoOff=function(event,arg){if(void 0===event&&(m_eventHandlers={},m_idleHandlers=[],m_deferredCount=0),Array.isArray(event))return event.forEach(function(e){m_this.geoOff(e,arg)}),m_this;if(arg){if(Array.isArray(arg))return arg.forEach(function(handler){m_this.geoOff(event,handler)}),m_this}else m_eventHandlers[event]=[];return m_eventHandlers.hasOwnProperty(event)&&(m_eventHandlers[event]=m_eventHandlers[event].filter(function(f){return f!==arg})),m_this},this._exit=function(){m_this.geoOff()},vgl.object.call(this),this},inherit(geo.object,vgl.object),geo.sceneObject=function(arg){"use strict";if(!(this instanceof geo.sceneObject))return new geo.sceneObject;geo.object.call(this,arg);var m_this=this,m_parent=null,m_children=[],s_exit=this._exit,s_trigger=this.geoTrigger,s_addDeferred=this.addDeferred,s_onIdle=this.onIdle;return this.addDeferred=function(defer){m_parent?m_parent.addDeferred(defer):s_addDeferred(defer)},this.onIdle=function(handler){m_parent?m_parent.onIdle(handler):s_onIdle(handler)},this.parent=function(arg){return void 0===arg?m_parent:(m_parent=arg,m_this)},this.addChild=function(child){return Array.isArray(child)?(child.forEach(m_this.addChild),m_this):(child.parent(m_this),m_children.push(child),m_this)},this.removeChild=function(child){return Array.isArray(child)?(child.forEach(m_this.removeChild),m_this):(m_children=m_children.filter(function(c){return c!==child}),m_this)},this.children=function(){return m_children.slice()},this.draw=function(arg){return m_this.children().forEach(function(child){child.draw(arg)}),m_this},this.geoTrigger=function(event,args,childrenOnly){var geoArgs;return args=args||{},geoArgs=args.geo||{},args.geo=geoArgs,geoArgs.stopPropagation?m_this:!childrenOnly&&m_parent&&geoArgs._triggeredBy!==m_parent?(geoArgs._triggeredBy=m_this,m_parent.geoTrigger(event,args),m_this):(s_trigger.call(m_this,event,args),geoArgs.stopPropagation?m_this:(m_children.forEach(function(child){geoArgs._triggeredBy=m_this,child.geoTrigger(event,args)}),m_this))},this._exit=function(){m_this.children=[],delete m_this.parent,s_exit()},this},inherit(geo.sceneObject,geo.object),geo.timestamp=function(){"use strict";return this instanceof geo.timestamp?void vgl.timestamp.call(this):new geo.timestamp},inherit(geo.timestamp,vgl.timestamp),geo.ellipsoid=function(x,y,z){"use strict";if(!(this instanceof geo.ellipsoid))return new geo.ellipsoid(x,y,z);if(x=vgl.defaultValue(x,0),y=vgl.defaultValue(y,0),z=vgl.defaultValue(z,0),0>x||0>y||0>z)return console.log("[error] Al radii components must be greater than zero");var m_this=this,m_radii=new vec3.fromValues(x,y,z),m_radiiSquared=new vec3.fromValues(x*x,y*y,z*z),m_minimumRadius=Math.min(x,y,z),m_maximumRadius=Math.max(x,y,z);return this.radii=function(){return m_radii},this.radiiSquared=function(){return m_radiiSquared},this.maximumRadius=function(){return m_maximumRadius},this.minimumRadius=function(){return m_minimumRadius},this.computeGeodeticSurfaceNormal=function(lat,lon){if("undefined"==typeof lat||"undefined"==typeof lon)throw"[error] Valid latitude and longitude is required";var cosLatitude=Math.cos(lat),result=vec3.create();return result[0]=cosLatitude*Math.cos(lon),result[1]=cosLatitude*Math.sin(lon),result[2]=Math.sin(lat),vec3.normalize(result,result),result},this.transformPoint=function(lat,lon,elev){lat*=Math.PI/180,lon*=Math.PI/180;var n=m_this.computeGeodeticSurfaceNormal(lat,lon),k=vec3.create(),gamma=Math.sqrt(vec3.dot(n,k)),result=vec3.create();return vec3.multiply(k,m_radiiSquared,n),vec3.scale(k,k,1/gamma),vec3.scale(n,n,elev),vec3.add(result,n,k),result},this.transformGeometry=function(geom){if(!geom)throw"[error] Failed to transform to cartesian. Invalid geometry.";var sourceData=geom.sourceData(vgl.vertexAttributeKeys.Position),sourceDataArray=sourceData.data(),noOfComponents=sourceData.attributeNumberOfComponents(vgl.vertexAttributeKeys.Position),stride=sourceData.attributeStride(vgl.vertexAttributeKeys.Position),offset=sourceData.attributeOffset(vgl.vertexAttributeKeys.Position),sizeOfDataType=sourceData.sizeOfAttributeDataType(vgl.vertexAttributeKeys.Position),index=null,count=sourceDataArray.length*(1/noOfComponents),gamma=null,n=null,j=0,k=vec3.create(),result=vec3.create();if(stride/=sizeOfDataType,offset/=sizeOfDataType,3!==noOfComponents)throw"[error] Requires positions with three components";for(j=0;count>j;j+=1)index=j*stride+offset,sourceDataArray[index]=sourceDataArray[index]*(Math.PI/180),sourceDataArray[index+1]=sourceDataArray[index+1]*(Math.PI/180),n=m_this.computeGeodeticSurfaceNormal(sourceDataArray[index+1],sourceDataArray[index]),vec3.multiply(k,m_radiiSquared,n),gamma=Math.sqrt(vec3.dot(n,k)),vec3.scale(k,k,1/gamma),vec3.scale(n,n,sourceDataArray[index+2]),vec3.add(result,n,k),sourceDataArray[index]=result[0],sourceDataArray[index+1]=result[1],sourceDataArray[index+2]=result[2]},m_this},geo.ellipsoid.WGS84=vgl.freezeObject(geo.ellipsoid(6378137,6378137,6356752.314245179)),geo.ellipsoid.UNIT_SPHERE=vgl.freezeObject(geo.ellipsoid(1,1,1)),geo.mercator={r_major:6378137},geo.mercator.r_minor=function(spherical){"use strict";var r_minor;return spherical=void 0!==spherical?spherical:!1,r_minor=spherical?6378137:6356752.314245179},geo.mercator.f=function(spherical){"use strict";return(geo.mercator.r_major-geo.mercator.r_minor(spherical))/geo.mercator.r_major},geo.mercator.long2tilex=function(lon,z){"use strict";var rad=(lon+180)/360,f=Math.floor(rad*Math.pow(2,z));return f},geo.mercator.lat2tiley=function(lat,z){"use strict";var rad=lat*Math.PI/180;return Math.floor((1-rad/Math.PI)/2*Math.pow(2,z))},geo.mercator.long2tilex2=function(lon,z){"use strict";var rad=(lon+180)/360,f=rad*Math.pow(2,z),ret=Math.floor(f),frac=f-ret;return[ret,frac]},geo.mercator.lat2tiley2=function(lat,z){"use strict";var rad=lat*Math.PI/180,f=(1-Math.log(Math.tan(rad)+1/Math.cos(rad))/Math.PI)/2*Math.pow(2,z),ret=Math.floor(f),frac=f-ret;return[ret,frac]},geo.mercator.tilex2long=function(x,z){"use strict";return x/Math.pow(2,z)*360-180},geo.mercator.tiley2lat=function(y,z){"use strict";var n=Math.PI-2*Math.PI*y/Math.pow(2,z);return 180/Math.PI*Math.atan(.5*(Math.exp(n)-Math.exp(-n)))},geo.mercator.y2lat=function(a){"use strict";return 180/Math.PI*(2*Math.atan(Math.exp(a*Math.PI/180))-Math.PI/2)},geo.mercator.lat2y=function(a){"use strict";return 180/Math.PI*Math.log(Math.tan(Math.PI/4+a*(Math.PI/180)/2))},geo.mercator.deg2rad=function(d){"use strict";var r=d*(Math.PI/180);return r},geo.mercator.rad2deg=function(r){"use strict";var d=r/(Math.PI/180);return d},geo.mercator.ll2m=function(lon,lat,spherical){"use strict";lat>89.5&&(lat=89.5),-89.5>lat&&(lat=-89.5);var x=this.r_major*this.deg2rad(lon),temp=this.r_minor(spherical)/this.r_major,es=1-temp*temp,eccent=Math.sqrt(es),phi=this.deg2rad(lat),sinphi=Math.sin(phi),con=eccent*sinphi,com=.5*eccent,con2=Math.pow((1-con)/(1+con),com),ts=Math.tan(.5*(.5*Math.PI-phi))/con2,y=-this.r_major*Math.log(ts),ret={x:x,y:y};return ret},geo.mercator.m2ll=function(x,y,spherical){"use strict";var lon=this.rad2deg(x/this.r_major),temp=this.r_minor(spherical)/this.r_major,e=Math.sqrt(1-temp*temp),lat=this.rad2deg(this.pjPhi2(Math.exp(-(y/this.r_major)),e)),ret={lon:lon,lat:lat};return ret},geo.mercator.pjPhi2=function(ts,e){"use strict";var con,dphi,N_ITER=15,HALFPI=Math.PI/2,TOL=1e-10,i=N_ITER,eccnth=.5*e,Phi=HALFPI-2*Math.atan(ts);do con=e*Math.sin(Phi),dphi=HALFPI-2*Math.atan(ts*Math.pow((1-con)/(1+con),eccnth))-Phi,Phi+=dphi,i-=1;while(Math.abs(dphi)>TOL&&i);return Phi},geo.layer=function(arg){"use strict";if(!(this instanceof geo.layer))return new geo.layer(arg);arg=arg||{},geo.sceneObject.call(this,arg);var m_this=this,s_exit=this._exit,m_style=void 0===arg.style?{opacity:.5,color:[.8,.8,.8],visible:!0,bin:100}:arg.style,m_id=void 0===arg.id?geo.layer.newLayerId():arg.id,m_name="",m_gcs="EPSG:4326",m_timeRange=null,m_source=arg.source||null,m_map=void 0===arg.map?null:arg.map,m_isReference=!1,m_x=0,m_y=0,m_width=0,m_height=0,m_node=null,m_canvas=null,m_renderer=null,m_initialized=!1,m_rendererName=void 0===arg.renderer?"vgl":arg.renderer,m_dataTime=geo.timestamp(),m_updateTime=geo.timestamp(),m_drawTime=geo.timestamp(),m_sticky=void 0===arg.sticky?!0:arg.sticky,m_active=void 0===arg.active?!0:arg.active,m_attribution=arg.attribution||null;return this.sticky=function(){return m_sticky},this.active=function(){return m_active},this.node=function(){return m_node},this.id=function(val){return void 0===val?m_id:(m_id=geo.newLayerId(),m_this.modified(),m_this)},this.name=function(val){return void 0===val?m_name:(m_name=val,m_this.modified(),m_this)},this.opacity=function(val){return void 0===val?m_style.opacity:(m_style.opacity=val,m_this.modified(),m_this)},this.visible=function(val){return void 0===val?m_style.visible:(m_style.visible=val,m_this.modified(),m_this)},this.bin=function(val){return void 0===val?m_style.bin:(m_style.bin=val,m_this.modified(),m_this)},this.gcs=function(val){return void 0===val?m_gcs:(m_gcs=val,m_this.modified(),m_this)},this.transform=function(val){return geo.transform.transformLayer(val,m_this,m_map.baseLayer()),m_this},this.timeRange=function(val){return void 0===val?m_timeRange:(m_timeRange=val,m_this.modified(),m_this)},this.source=function(val){return void 0===val?m_source:(m_source=val,m_this.modified(),m_this)},this.map=function(val){return void 0===val?m_map:(m_map=val,m_map.node().append(m_node),m_this.modified(),m_this)},this.renderer=function(){return m_renderer},this.canvas=function(){return m_canvas},this.viewport=function(){return[m_x,m_y,m_width,m_height]},this.dataTime=function(){return m_dataTime},this.updateTime=function(){return m_updateTime},this.drawTime=function(){return m_drawTime},this.query=function(){},this.referenceLayer=function(val){return void 0!==val?(m_isReference=val,m_this.modified(),m_this):m_isReference},this.initialized=function(val){return void 0!==val?(m_initialized=val,m_this):m_initialized},this.toLocal=function(input){return input},this.fromLocal=function(input){return input},this.attribution=function(arg){return void 0!==arg?(m_attribution=arg,m_this.map().updateAttribution(),m_this):m_attribution},this._init=function(){if(m_initialized)return m_this;m_node=$(document.createElement("div")),m_node.attr("id",m_name),m_node.css("position","absolute"),m_map&&m_map.node().append(m_node);var options=$.extend({},arg);return delete options.map,m_canvas?m_renderer=geo.createRenderer(m_rendererName,m_this,m_canvas,options):(m_renderer=geo.createRenderer(m_rendererName,m_this,void 0,options),m_canvas=m_renderer.canvas()),m_this.active()||m_node.css("pointerEvents","none"),m_initialized=!0,m_this},this._exit=function(){m_renderer._exit(),m_node.off(),m_node.remove(),m_node=null,arg={},m_canvas=null,m_renderer=null,s_exit()},this._update=function(){},this._resize=function(x,y,w,h){return m_x=x,m_y=y,m_width=w,m_height=h,m_node.width(w),m_node.height(h),m_this.modified(),m_this.geoTrigger(geo.event.resize,{x:x,y:y,width:m_width,height:m_height}),m_this},this.width=function(){return m_width},this.height=function(){return m_height},this},geo.layer.newLayerId=function(){"use strict";var currentId=1;return function(){var id=currentId;return currentId+=1,id}}(),geo.layer.create=function(map,spec){"use strict";if(spec=spec||{},spec.type="feature","feature"!==spec.type)return console.warn("Unsupported layer type"),null;if(spec.renderer=spec.renderer||"vgl","d3"!==spec.renderer&&"vgl"!==spec.renderer)return console.warn("Invalid renderer"),null;var layer=map.createLayer(spec.type,spec);return layer?(spec.features.forEach(function(f){f.data=f.data||spec.data,f.feature=geo.feature.create(layer,f)}),layer):(console.warn("Unable to create a layer"),null)},inherit(geo.layer,geo.sceneObject),geo.featureLayer=function(arg){"use strict";if(!(this instanceof geo.featureLayer))return new geo.featureLayer(arg);geo.layer.call(this,arg);var m_this=this,m_features=[],s_init=this._init,s_exit=this._exit,s_update=this._update,s_draw=this.draw;return this.createFeature=function(featureName,arg){var newFeature=geo.createFeature(featureName,m_this,m_this.renderer(),arg);return m_this.addChild(newFeature),m_features.push(newFeature),m_this.features(m_features),m_this.modified(),newFeature},this.deleteFeature=function(feature){var i;for(i=0;im_this.updateTime().getMTime())for(i=0;i=deltaT?30:deltaT;var sf=springForce(),speed=calcSpeed(v),vx=v.x/speed,vy=v.y/speed;return speed*=Math.exp(-m_options.momentum.drag*deltaT),calcSpeed(sf)*deltaT+speed0?(vx*=speed,vy*=speed):(vx=0,vy=0),{x:vx-sf.x*deltaT,y:vy-sf.y*deltaT})}function springForce(){var xplus,xminus,yplus,yminus;if(!m_options.spring.enabled)return{x:0,y:0};var ul=m_this.map().gcsToDisplay({x:-180,y:82}),lr=m_this.map().gcsToDisplay({x:180,y:-82}),c=m_options.spring.springConstant,width=m_this.map().node().width(),height=m_this.map().node().height();return xplus=c*Math.max(0,ul.x),xminus=c*Math.max(0,width-lr.x),yplus=c*Math.max(0,ul.y)/2,yminus=c*Math.max(0,height-lr.y)/2,{x:xplus-xminus,y:yplus-yminus}}if(!(this instanceof geo.mapInteractor))return new geo.mapInteractor(args);geo.object.call(this);var m_mouse,m_keyboard,m_state,$node,m_options=args||{},m_this=this,m_wheelQueue={x:0,y:0},m_throttleTime=10,m_wait=!1,m_disableThrottle=!0,m_selectionLayer=null,m_selectionPlane=null;return m_options=$.extend(!0,{panMoveButton:"left",panMoveModifiers:{},zoomMoveButton:"right",zoomMoveModifiers:{},panWheelEnabled:!1,panWheelModifiers:{},zoomWheelEnabled:!0,zoomWheelModifiers:{},wheelScaleX:1,wheelScaleY:1,zoomScale:1,selectionButton:"left",selectionModifiers:{shift:!0},momentum:{enabled:!0,maxSpeed:2.5,minSpeed:.01,drag:.01},spring:{enabled:!1,springConstant:5e-5}},m_options),m_mouse={page:{x:0,y:0},map:{x:0,y:0},buttons:{left:!1,right:!1,middle:!1},modifiers:{alt:!1,ctrl:!1,shift:!1,meta:!1},time:new Date,deltaTime:1,velocity:{x:0,y:0}},m_keyboard={},m_state={},this._connectEvents=function(){return m_options.map?(m_this._disconnectEvents(),$node=$(m_options.map.node()),$node.on("mousemove.geojs",m_this._handleMouseMove),$node.on("mousedown.geojs",m_this._handleMouseDown),$node.on("mouseup.geojs",m_this._handleMouseUp),$node.on("wheel.geojs",m_this._handleMouseWheel),("right"===m_options.panMoveButton||"right"===m_options.zoomMoveButton)&&$node.on("contextmenu.geojs",function(){return!1}),m_this):m_this},this._disconnectEvents=function(){return $node&&($node.off(".geojs"),$node=null),m_this},this.map=function(val){return void 0!==val?(m_options.map=val,m_this._connectEvents(),m_this):m_options.map},this.options=function(opts){return void 0===opts?$.extend({},m_options):($.extend(m_options,opts),m_this)},this._getMousePosition=function(evt){var dt,t,offset=$node.offset();t=(new Date).valueOf(),dt=t-m_mouse.time,m_mouse.time=t,m_mouse.deltaTime=dt,m_mouse.velocity={x:(evt.pageX-m_mouse.page.x)/dt,y:(evt.pageY-m_mouse.page.y)/dt},m_mouse.page={x:evt.pageX,y:evt.pageY},m_mouse.map={x:evt.pageX-offset.left,y:evt.pageY-offset.top};try{m_mouse.geo=m_this.map().displayToGcs(m_mouse.map)}catch(e){m_mouse.geo=null}},this._getMouseButton=function(evt){1===evt.which?m_mouse.buttons.left="mouseup"!==evt.type:3===evt.which?m_mouse.buttons.right="mouseup"!==evt.type:2===evt.which&&(m_mouse.buttons.middle="mouseup"!==evt.type)},this._getMouseModifiers=function(evt){m_mouse.modifiers.alt=evt.altKey,m_mouse.modifiers.ctrl=evt.ctrlKey,m_mouse.modifiers.meta=evt.metaKey,m_mouse.modifiers.shift=evt.shiftKey},this._getSelection=function(){var origin=m_state.origin,mouse=m_this.mouse(),map=m_this.map(),display={},gcs={};return display.upperLeft={x:Math.min(origin.map.x,mouse.map.x),y:Math.min(origin.map.y,mouse.map.y)},display.lowerRight={x:Math.max(origin.map.x,mouse.map.x),y:Math.max(origin.map.y,mouse.map.y)},display.upperRight={x:display.lowerRight.x,y:display.upperLeft.y},display.lowerLeft={x:display.upperLeft.x,y:display.lowerRight.y},gcs.upperLeft=map.displayToGcs(display.upperLeft),gcs.lowerRight=map.displayToGcs(display.lowerRight),gcs.upperRight=map.displayToGcs(display.upperRight),gcs.lowerLeft=map.displayToGcs(display.lowerLeft),m_selectionPlane.origin([display.lowerLeft.x,display.lowerLeft.y,0]),m_selectionPlane.upperLeft([display.upperLeft.x,display.upperLeft.y,0]),m_selectionPlane.lowerRight([display.lowerRight.x,display.lowerRight.y,0]),m_selectionPlane.draw(),{display:display,gcs:gcs,mouse:mouse,origin:$.extend({},m_state.origin)}},this.cancel=function(action){var out;return out=action?m_state.action===action:!!m_state.action,out&&(m_state={}),out},this._handleMouseDown=function(evt){var action=null;"momentum"===m_state.action&&(m_state={}),m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),eventMatch(m_options.panMoveButton,m_options.panMoveModifiers)?action="pan":eventMatch(m_options.zoomMoveButton,m_options.zoomMoveModifiers)?action="zoom":eventMatch(m_options.selectionButton,m_options.selectionModifiers)&&(action="select"),m_mouse.velocity={x:0,y:0},action&&(m_state={action:action,origin:$.extend(!0,{},m_mouse),delta:{x:0,y:0}},"select"===action&&(m_selectionLayer&&(m_selectionLayer.clear(),m_this.map().deleteLayer(m_selectionLayer),m_selectionLayer=null),m_selectionLayer=m_this.map().createLayer("feature",{renderer:"d3"}),m_selectionPlane=m_selectionLayer.createFeature("plane"),m_selectionPlane.style({screenCoordinates:!0,fillOpacity:function(){return.25}}),m_this.map().geoTrigger(geo.event.brushstart,m_this._getSelection())),$(document).on("mousemove.geojs",m_this._handleMouseMoveDocument),$(document).on("mouseup.geojs",m_this._handleMouseUpDocument))},this._handleMouseMove=function(evt){m_state.action||(m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_this.map().geoTrigger(geo.event.mousemove,m_this.mouse()))},this._handleMouseMoveDocument=function(evt){var dx,dy,selectionObj;return m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_state.action?void(doRespond()&&(dx=m_mouse.map.x-m_state.origin.map.x-m_state.delta.x,dy=m_mouse.map.y-m_state.origin.map.y-m_state.delta.y,
-m_state.delta.x+=dx,m_state.delta.y+=dy,"pan"===m_state.action?m_this.map().pan({x:dx,y:dy}):"zoom"===m_state.action?m_this.map().zoom(m_this.map().zoom()-dy*m_options.zoomScale/120):"select"===m_state.action&&(selectionObj=m_this._getSelection(),m_this.map().geoTrigger(geo.event.brush,selectionObj)),evt.preventDefault())):void console.log("WARNING: Invalid state in mapInteractor.")},this._handleMouseUpDocument=function(evt){var selectionObj,oldAction;m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),$(document).off(".geojs"),m_mouse.buttons.right&&evt.preventDefault(),"select"===m_state.action&&(selectionObj=m_this._getSelection(),m_selectionLayer.clear(),m_this.map().deleteLayer(m_selectionLayer),m_selectionLayer=null,m_selectionPlane=null,m_this.map().geoTrigger(geo.event.brushend,selectionObj)),oldAction=m_state.action,m_state={},m_options.momentum.enabled&&"pan"===oldAction&&m_this.springBack(!0)},this._handleMouseUp=function(evt){m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_this.map().geoTrigger(geo.event.mouseclick,m_this.mouse())},this._handleMouseWheel=function(evt){var zoomFactor,direction;return evt.deltaFactor=1,1===evt.originalEvent.deltaMode?evt.deltaFactor=12:2===evt.originalEvent.deltaMode&&(evt.deltaFactor=$(window).height()),evt.deltaX=evt.originalEvent.deltaX||0,evt.deltaY=evt.originalEvent.deltaY||0,m_this._getMouseModifiers(evt),evt.deltaX=evt.deltaX*m_options.wheelScaleX*evt.deltaFactor/120,evt.deltaY=evt.deltaY*m_options.wheelScaleY*evt.deltaFactor/120,evt.preventDefault(),doRespond()?(evt.deltaX+=m_wheelQueue.x,evt.deltaY+=m_wheelQueue.y,m_wheelQueue={x:0,y:0},void(m_options.panWheelEnabled&&eventMatch("wheel",m_options.panWheelModifiers)?m_this.map().pan({x:evt.deltaX,y:-evt.deltaY}):m_options.zoomWheelEnabled&&eventMatch("wheel",m_options.zoomWheelModifiers)&&(zoomFactor=-evt.deltaY,direction=m_mouse.map,m_this.map().zoom(m_this.map().zoom()+zoomFactor,direction)))):(m_wheelQueue.x+=evt.deltaX,void(m_wheelQueue.y+=evt.deltaY))},this.springBack=function(initialVelocity){"momentum"!==m_state.action&&(initialVelocity||(m_mouse.velocity={x:0,y:0}),m_state.action="momentum",m_state.origin=m_this.mouse(),m_state.start=new Date,m_state.handler=function(){var v,s,last,dt;if(dt=Math.min(m_mouse.deltaTime,30),"momentum"===m_state.action&&m_this.map()&&!m_this.map().transition()){if(last=m_state.start.valueOf(),m_state.start=new Date,v=modifyVelocity(m_mouse.velocity,m_state.start-last),!v)return void(m_state={});s=calcSpeed(v),s>m_options.momentum.maxSpeed&&(s=m_options.momentum.maxSpeed/s,v.x=v.x*s,v.y=v.y*s),isFinite(v.x)&&isFinite(v.y)||(v.x=0,v.y=0),m_mouse.velocity.x=v.x,m_mouse.velocity.y=v.y,m_this.map().pan({x:m_mouse.velocity.x*dt,y:m_mouse.velocity.y*dt}),m_state.handler&&window.requestAnimationFrame(m_state.handler)}},m_state.handler&&window.requestAnimationFrame(m_state.handler))},this._handleDoubleClick=function(){},this.destroy=function(){m_this._disconnectEvents(),m_this.map(null)},this.mouse=function(){return $.extend(!0,{},m_mouse)},this.keyboard=function(){return $.extend(!0,{},m_keyboard)},this.state=function(){return $.extend(!0,{},m_state)},this.simulateEvent=function(type,options){var evt,page,offset,which;return m_this.map()?(page=options.page||{},options.map&&(offset=$node.offset(),page.x=options.map.x+offset.left,page.y=options.map.y+offset.top),"left"===options.button?which=1:"right"===options.button?which=3:"middle"===options.button&&(which=2),options.modifiers=options.modifiers||[],options.wheelDelta=options.wheelDelta||{},evt=$.Event(type,{pageX:page.x,pageY:page.y,which:which,altKey:options.modifiers.indexOf("alt")>=0,ctrlKey:options.modifiers.indexOf("ctrl")>=0,metaKey:options.modifiers.indexOf("meta")>=0,shiftKey:options.modifiers.indexOf("shift")>=0,originalEvent:{deltaX:options.wheelDelta.x,deltaY:options.wheelDelta.y,deltaMode:options.wheelMode}}),void $node.trigger(evt)):m_this},this._connectEvents(),this},inherit(geo.mapInteractor,geo.object),geo.clock=function(opts){"use strict";if(!(this instanceof geo.clock))return new geo.clock(opts);opts=opts||{},geo.object.call(this,opts);var m_this=this,m_now=new Date(0),m_start=null,m_end=null,m_step=null,m_rate=null,m_loop=Number.POSITIVE_INFINITY,m_currentLoop=0,m_state="stop",m_currentAnimation=null,m_object=null;this.object=function(arg){return void 0===arg?m_object:(m_object=arg,m_this)},this._attached=function(){return m_object instanceof geo.object},this.now=function(arg){var previous=m_now;return void 0===arg?m_now:(m_now=arg,m_now!==previous&&m_this._attached()&&m_this.object().geoTrigger(geo.event.clock.change,{previous:previous,current:m_now,clock:m_this}),m_this)},this.start=function(arg){return void 0===arg?m_start:(m_start=arg,m_this)},this.end=function(arg){return void 0===arg?m_end:(m_end=arg,m_this)},this.step=function(arg){return void 0===arg?m_step:(m_step=arg,m_this)},this.loop=function(arg){return void 0===arg?m_loop:(m_loop=arg,m_this)},this.state=function(arg,step){return void 0===arg?m_state:["stop","play","pause"].indexOf(arg)<0?(console.log("WARNING: Ignored invalid state: "+arg),m_this):("play"===arg&&"stop"===m_state&&(m_currentLoop=0,m_this.now(m_this.start())),"play"===arg&&"play"!==m_state&&(m_state=arg,m_this._animate(step||1)),m_state=arg,m_this)},this.framerate=function(arg){return void 0===arg?m_rate:(m_rate=arg,m_this)},this.stepForward=function(){return m_this.state("pause"),m_this._setNextFrame(1),m_this},this.stepBackward=function(){return m_this.state("pause"),m_this._setNextFrame(-1),m_this},this._setNextFrame=function(step){var next=new Date(m_this.now().valueOf()+step*m_this.step());return next>=m_this.end()||next<=m_this.start()?m_this.loop()<=m_currentLoop?void m_this.state("stop"):(m_currentLoop+=1,void(step>=0?m_this.now(m_this.start()):m_this.now(m_this.end()))):void m_this.now(next)},this._animate=function(step){function frame(){myAnimation===m_currentAnimation&&(m_this._setNextFrame(step),"play"===m_this.state()?m_this.framerate()?window.setTimeout(frame,1e3/m_this.framerate()):window.requestAnimationFrame(frame):m_this._attached()&&m_this.object().geoTrigger(geo.event.clock[m_this.state()],{current:m_this.now(),clock:m_this}))}var myAnimation={};m_currentAnimation=myAnimation,m_this._attached()&&m_this.object().geoTrigger(geo.event.clock.play,{current:m_this.now(),clock:m_this}),m_this.framerate()?window.setTimeout(frame,1e3/m_this.framerate()):window.requestAnimationFrame(frame)}},inherit(geo.clock,geo.object),geo.fileReader=function(arg){"use strict";function newFileReader(done,progress){var reader=new FileReader;return progress&&(reader.onprogress=progress),reader.onloadend=function(){reader.result||done(reader.error),done(reader.result)},reader}if(!(this instanceof geo.fileReader))return new geo.fileReader(arg);if(geo.object.call(this),arg=arg||{},!(arg.layer instanceof geo.featureLayer))throw"fileReader must be given a feature layer";var m_layer=arg.layer;return this.layer=function(){return m_layer},this.canRead=function(){return!1},this.read=function(file,done){done(!1)},this._getString=function(file,done,progress){var reader=newFileReader(done,progress);reader.readAsText(file)},this._getArrayBuffer=function(file,done,progress){var reader=newFileReader(done,progress);reader.readAsText(file)},this},inherit(geo.fileReader,geo.object),geo.jsonReader=function(arg){"use strict";if(!(this instanceof geo.jsonReader))return new geo.jsonReader(arg);var m_this=this,m_style=arg.style||{};m_style=$.extend({strokeWidth:2,strokeColor:{r:0,g:0,b:0},strokeOpacity:1,fillColor:{r:1,g:0,b:0},fillOpacity:1},m_style),geo.fileReader.call(this,arg),this.canRead=function(file){if(file instanceof File)return"application/json"===file.type||file.name.match(/\.json$/);if("string"==typeof file){try{JSON.parse(file)}catch(e){return!1}return!0}try{if(Array.isArray(m_this._featureArray(file)))return!0}catch(e){}return!1},this._readObject=function(file,done,progress){function onDone(fileString){"string"!=typeof fileString&&done(!1);try{object=JSON.parse(fileString),done(object)}catch(e){object||$.ajax({type:"GET",url:fileString,dataType:"text"}).done(function(data){object=JSON.parse(data),done(object)}).fail(function(){done(!1)})}}var object;file instanceof File?m_this._getString(file,onDone,progress):"string"==typeof file?onDone(file):done(file)},this._featureArray=function(spec){if("FeatureCollection"===spec.type)return spec.features||[];if("GeometryCollection"===spec.type)throw"GeometryCollection not yet implemented.";if(Array.isArray(spec.coordinates))return spec;throw"Unsupported collection type: "+spec.type},this._featureType=function(spec){var geometry=spec.geometry||{};return"Point"===geometry.type||"MultiPoint"===geometry.type?"point":"LineString"===geometry.type?"line":"Polygon"===geometry.type?"polygon":"MultiPolygon"===geometry.type?"multipolygon":null},this._getCoordinates=function(spec){var elv,geometry=spec.geometry||{},coordinates=geometry.coordinates||[];return(2===coordinates.length||3===coordinates.length)&&isFinite(coordinates[0])&&isFinite(coordinates[1])?(isFinite(coordinates[2])&&(elv=coordinates[2]),[{x:coordinates[0],y:coordinates[1],z:elv}]):(Array.isArray(coordinates[0][0])&&(coordinates=coordinates[0]),coordinates.map(function(c){return{x:c[0],y:c[1],z:c[2]}}))},this._getStyle=function(spec){return spec.properties},this.read=function(file,done,progress){function _done(object){var features,allFeatures=[];features=m_this._featureArray(object),features.forEach(function(feature){var type=m_this._featureType(feature),coordinates=m_this._getCoordinates(feature),style=m_this._getStyle(feature);type?"line"===type?(style.fill=style.fill||!1,allFeatures.push(m_this._addFeature(type,[coordinates],style,feature.properties))):"point"===type?(style.stroke=style.stroke||!1,allFeatures.push(m_this._addFeature(type,coordinates,style,feature.properties))):"polygon"===type?(style.fill=void 0===style.fill?!0:style.fill,style.fillOpacity=void 0===style.fillOpacity?.25:style.fillOpacity,allFeatures.push(m_this._addFeature("line",[coordinates],style,feature.properties))):"multipolygon"===type&&(style.fill=void 0===style.fill?!0:style.fill,style.fillOpacity=void 0===style.fillOpacity?.25:style.fillOpacity,coordinates=feature.geometry.coordinates.map(function(c){return c[0].map(function(el){return{x:el[0],y:el[1],z:el[2]}})}),allFeatures.push(m_this._addFeature("line",coordinates,style,feature.properties))):console.log("unsupported feature type: "+feature.geometry.type)}),done&&done(allFeatures)}m_this._readObject(file,_done,progress)},this._buildData=function(coordinates,properties,style){return coordinates.map(function(coord){return{coordinates:coord,properties:properties,style:style}})},this._addFeature=function(type,coordinates,style,properties){var _style=$.extend({},m_style,style),feature=m_this.layer().createFeature(type).data(m_this._buildData(coordinates,properties,style)).style(_style);return"line"===type?feature.line(function(d){return d.coordinates}):feature.position(function(d){return d.coordinates}),feature}},inherit(geo.jsonReader,geo.fileReader),geo.registerFileReader("jsonReader",geo.jsonReader),geo.map=function(arg){"use strict";function resizeSelf(){m_this.resize(0,0,m_node.width(),m_node.height())}if(!(this instanceof geo.map))return new geo.map(arg);arg=arg||{},geo.sceneObject.call(this,arg),arg.layers=void 0===arg.layers?[]:arg.layers;var m_this=this,s_exit=this._exit,m_x=0,m_y=0,m_node=$(arg.node),m_width=arg.width||m_node.width(),m_height=arg.height||m_node.height(),m_gcs=void 0===arg.gcs?"EPSG:4326":arg.gcs,m_uigcs=void 0===arg.uigcs?"EPSG:4326":arg.uigcs,m_center={x:0,y:0},m_zoom=void 0===arg.zoom?4:arg.zoom,m_baseLayer=null,m_fileReader=null,m_interactor=null,m_validZoomRange={min:0,max:16},m_transition=null,m_queuedTransition=null,m_clock=null,m_parallelProjection=arg.parallelProjection?!0:!1,m_discreteZoom=arg.discreteZoom?!0:!1,m_bounds={};return arg.center=geo.util.normalizeCoordinates(arg.center),arg.autoResize=void 0===arg.autoResize?!0:arg.autoResize,arg.clampBounds=void 0===arg.clampBounds?!0:arg.clampBounds,this.gcs=function(arg){return void 0===arg?m_gcs:(m_gcs=arg,m_this)},this.uigcs=function(){return m_uigcs},this.node=function(){return m_node},this.zoom=function(val,direction,ignoreDiscreteZoom){var base,evt,recenter=!1;return void 0===val?m_zoom:(m_discreteZoom&&val!==Math.round(val)&&!ignoreDiscreteZoom&&(m_zoom!==Math.round(m_zoom)||Math.abs(val-m_zoom)<.01?val=Math.round(m_zoom):m_zoom>val?val=Math.min(Math.round(val),m_zoom-1):val>m_zoom&&(val=Math.max(Math.round(val),m_zoom+1))),val=Math.min(m_validZoomRange.max,Math.max(val,m_validZoomRange.min)),val===m_zoom?m_this:(base=m_this.baseLayer(),evt={geo:{},zoomLevel:val,screenPosition:direction,eventType:geo.event.zoom},base&&base.geoTrigger(geo.event.zoom,evt,!0),recenter=evt.center,evt.geo.preventDefault||(m_zoom=val,m_this._updateBounds(),m_this.children().forEach(function(child){child.geoTrigger(geo.event.zoom,evt,!0)}),m_this.modified()),evt.center?m_this.center(recenter):m_this.pan({x:0,y:0}),m_this))},this.pan=function(delta,force){var evt,pt,corner1,corner2,base=m_this.baseLayer();return arg.clampBounds&&!force&&m_width&&m_height&&(pt=m_this.displayToGcs({x:delta.x,y:delta.y}),corner1=m_this.gcsToDisplay({x:-180,y:82}),corner2=m_this.gcsToDisplay({x:180,y:-82}),corner1.x>0&&corner2.x0&&corner2.y0||input instanceof Object))throw"Conversion method latLonToDisplay does not handle "+input;return world=m_baseLayer.toLocal(input),output=m_baseLayer.renderer().worldToDisplay(world)},this.displayToGcs=function(input){var output;if(!(input instanceof Array&&input.length>0||input instanceof Object))throw"Conversion method displayToGcs does not handle "+input;return output=m_baseLayer.renderer().displayToWorld(input),output=m_baseLayer.fromLocal(output)},this.query=function(){},this.baseLayer=function(baseLayer){var save;return void 0!==baseLayer?(m_gcs!==baseLayer.gcs()&&m_this.gcs(baseLayer.gcs()),m_baseLayer=baseLayer,m_baseLayer.referenceLayer(!0),arg.center&&m_this.center(arg.center,!0),save=m_zoom,m_zoom=null,m_this.zoom(save),m_this._updateBounds(),m_this.pan({x:0,y:0}),m_this):m_baseLayer},this.draw=function(){var i,layers=m_this.children();for(m_this.geoTrigger(geo.event.draw,{type:geo.event.draw,target:m_this}),m_this._update(),i=0;i=m_transition.end.time||next)return next||(m_this.center(m_transition.end.center),m_this.zoom(m_transition.end.zoom)),m_transition=null,m_this.geoTrigger(geo.event.transitionend,defaultOpts),done&&done(),void(next&&(m_queuedTransition=null,m_this.transition(next)));var z=m_transition.ease((time-m_transition.start.time)/defaultOpts.duration),p=m_transition.interp(z);m_transition.zCoord&&(p[2]=z2zoom(p[2])),m_this.center({x:p[0],y:p[1]}),m_this.zoom(p[2],void 0,!0),window.requestAnimationFrame(anim)}if(void 0===opts)return m_transition;if(m_transition)return m_queuedTransition=opts,m_this;var defaultOpts={center:m_this.center(),zoom:m_this.zoom(),duration:1e3,ease:function(t){return t},interp:defaultInterp,done:null,zCoord:!0};return opts.center&&(opts.center=geo.util.normalizeCoordinates(opts.center)),$.extend(defaultOpts,opts),m_transition={start:{center:m_this.center(),zoom:m_this.zoom()},end:{center:defaultOpts.center,zoom:m_discreteZoom?Math.round(defaultOpts.zoom):defaultOpts.zoom},ease:defaultOpts.ease,zCoord:defaultOpts.zCoord,done:defaultOpts.done,duration:defaultOpts.duration},defaultOpts.zCoord?m_transition.interp=defaultOpts.interp([m_transition.start.center.x,m_transition.start.center.y,zoom2z(m_transition.start.zoom)],[m_transition.end.center.x,m_transition.end.center.y,zoom2z(m_transition.end.zoom)]):m_transition.interp=defaultOpts.interp([m_transition.start.center.x,m_transition.start.center.y,m_transition.start.zoom],[m_transition.end.center.x,m_transition.end.center.y,m_transition.end.zoom]),m_this.geoTrigger(geo.event.transitionstart,defaultOpts),defaultOpts.cancelNavigation?(m_this.geoTrigger(geo.event.transitionend,defaultOpts),m_this):(defaultOpts.cancelAnimation?(defaultOpts.duration=0,anim(0)):window.requestAnimationFrame(anim),m_this)},this._updateBounds=function(){m_bounds.lowerLeft=m_this.displayToGcs({x:0,y:m_height}),m_bounds.lowerRight=m_this.displayToGcs({x:m_width,y:m_height}),m_bounds.upperLeft=m_this.displayToGcs({x:0,y:0}),m_bounds.upperRight=m_this.displayToGcs({x:m_width,y:0})},this.bounds=function(bds){var nav;return void 0===bds?m_bounds:(nav=m_this.zoomAndCenterFromBounds(bds),m_this.zoom(nav.zoom),m_this.center(nav.center),m_bounds)},this.zoomAndCenterFromBounds=function(bds){var ll,ur,dx,dy,zx,zy,center,zoom;if(ll=geo.util.normalizeCoordinates(bds.lowerLeft||{}),ur=geo.util.normalizeCoordinates(bds.upperRight||{}),ll.x>=ur.x||ll.y>=ur.y)throw new Error("Invalid bounds provided");return center={x:(ll.x+ur.x)/2,y:(ll.y+ur.y)/2},dx=m_bounds.upperRight.x-m_bounds.lowerLeft.x,dy=m_bounds.upperRight.y-m_bounds.lowerLeft.y,zx=m_zoom-Math.log2((ur.x-ll.x)/dx),zy=m_zoom-Math.log2((ur.y-ll.y)/dy),zoom=Math.min(zx,zy),m_discreteZoom&&(zoom=Math.floor(zoom)),{zoom:zoom,center:center}},this.discreteZoom=function(discreteZoom){return void 0===discreteZoom?m_discreteZoom:(discreteZoom=discreteZoom?!0:!1,m_discreteZoom!==discreteZoom&&(m_discreteZoom=discreteZoom,m_discreteZoom&&m_this.zoom(Math.round(m_this.zoom()))),m_this)},this.updateAttribution=function(){m_this.node().find(".geo-attribution").remove();var $a=$("").addClass("geo-attribution").css({position:"absolute",right:"0px",bottom:"0px","padding-right":"5px",cursor:"auto",font:'11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif',"z-index":"1001",background:"rgba(255,255,255,0.7)",clear:"both",display:"block","pointer-events":"auto"}).on("mousedown",function(evt){evt.stopPropagation()});return m_this.children().forEach(function(layer){var content=layer.attribution();content&&$("").addClass("geo-attribution-layer").css({"padding-left":"5px"}).html(content).appendTo($a)}),$a.appendTo(m_this.node()),m_this},this.interactor(arg.interactor||geo.mapInteractor()),this.clock(arg.clock||geo.clock()),arg.autoResize&&$(window).resize(resizeSelf),m_this.geoOn([geo.event.layerAdd,geo.event.layerRemove],m_this.updateAttribution),this},geo.map.create=function(spec){"use strict";var map=geo.map(spec);return map?(spec.data=spec.data||[],spec.layers=spec.layers||[],spec.layers.forEach(function(l){l.data=l.data||spec.data,l.layer=geo.layer.create(map,l)}),map):(console.warn("Could not create map."),null)},inherit(geo.map,geo.sceneObject),geo.feature=function(arg){"use strict";if(!(this instanceof geo.feature))return new geo.feature(arg);geo.sceneObject.call(this),arg=arg||{};var m_this=this,s_exit=this._exit,m_selectionAPI=void 0===arg.selectionAPI?!1:arg.selectionAPI,m_style={},m_layer=void 0===arg.layer?null:arg.layer,m_gcs=void 0===arg.gcs?"EPSG:4326":arg.gcs,m_visible=void 0===arg.visible?!0:arg.visible,m_bin=void 0===arg.bin?0:arg.bin,m_renderer=void 0===arg.renderer?null:arg.renderer,m_dataTime=geo.timestamp(),m_buildTime=geo.timestamp(),m_updateTime=geo.timestamp(),m_selectedFeatures=[];return this._bindMouseHandlers=function(){m_selectionAPI&&(m_this._unbindMouseHandlers(),m_this.geoOn(geo.event.mousemove,m_this._handleMousemove),m_this.geoOn(geo.event.mouseclick,m_this._handleMouseclick),m_this.geoOn(geo.event.brushend,m_this._handleBrushend),m_this.geoOn(geo.event.brush,m_this._handleBrush))},this._unbindMouseHandlers=function(){m_this.geoOff(geo.event.mousemove,m_this._handleMousemove),m_this.geoOff(geo.event.mouseclick,m_this._handleMouseclick),m_this.geoOff(geo.event.brushend,m_this._handleBrushend),m_this.geoOff(geo.event.brush,m_this._handleBrush)},this.pointSearch=function(){return{index:[],found:[]}},this._handleMousemove=function(){var mouse=m_this.layer().map().interactor().mouse(),data=m_this.data(),over=m_this.pointSearch(mouse.geo),newFeatures=[],oldFeatures=[],lastTop=-1,top=-1;m_selectedFeatures.length&&(lastTop=m_selectedFeatures[m_selectedFeatures.length-1]),newFeatures=over.index.filter(function(i){return m_selectedFeatures.indexOf(i)<0}),oldFeatures=m_selectedFeatures.filter(function(i){return over.index.indexOf(i)<0}),geo.feature.eventID+=1,newFeatures.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseover,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===newFeatures.length-1},!0)}),geo.feature.eventID+=1,oldFeatures.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseout,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===oldFeatures.length-1},!0)}),geo.feature.eventID+=1,over.index.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mousemove,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===over.index.length-1},!0)}),m_selectedFeatures=over.index,m_selectedFeatures.length&&(top=m_selectedFeatures[m_selectedFeatures.length-1]),lastTop!==top&&(-1!==lastTop&&m_this.geoTrigger(geo.event.feature.mouseoff,{data:data[lastTop],index:lastTop,mouse:mouse},!0),-1!==top&&m_this.geoTrigger(geo.event.feature.mouseon,{data:data[top],index:top,mouse:mouse},!0))},this._handleMouseclick=function(){var mouse=m_this.layer().map().interactor().mouse(),data=m_this.data(),over=m_this.pointSearch(mouse.geo);geo.feature.eventID+=1,over.index.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseclick,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===over.index.length-1},!0)})},this._handleBrush=function(brush){var idx=m_this.boxSearch(brush.gcs.lowerLeft,brush.gcs.upperRight),data=m_this.data();geo.feature.eventID+=1,idx.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.brush,{data:data[i],index:i,mouse:brush.mouse,brush:brush,eventID:geo.feature.eventID,top:idx===idx.length-1},!0)})},this._handleBrushend=function(brush){var idx=m_this.boxSearch(brush.gcs.lowerLeft,brush.gcs.upperRight),data=m_this.data();geo.feature.eventID+=1,idx.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.brushend,{data:data[i],index:i,mouse:brush.mouse,brush:brush,eventID:geo.feature.eventID,top:idx===idx.length-1},!0)})},this.style=function(arg1,arg2){return void 0===arg1?m_style:"string"==typeof arg1&&void 0===arg2?m_style[arg1]:void 0===arg2?(m_style=$.extend({},m_style,arg1),m_this.modified(),m_this):(m_style[arg1]=arg2,m_this.modified(),m_this)},this.style.get=function(key){var tmp,out;if(void 0===key){var k,all={};for(k in m_style)m_style.hasOwnProperty(k)&&(all[k]=m_this.style.get(k));return all}return key.toLowerCase().match(/color$/)?geo.util.isFunction(m_style[key])?(tmp=geo.util.ensureFunction(m_style[key]),out=function(){return geo.util.convertColor(tmp.apply(this,arguments))}):out=geo.util.ensureFunction(geo.util.convertColor(m_style[key])):out=geo.util.ensureFunction(m_style[key]),out},this.layer=function(){return m_layer},this.renderer=function(){return m_renderer},this.drawables=function(){return m_this._drawables()},this.gcs=function(val){return void 0===val?m_gcs:(m_gcs=val,m_this.modified(),m_this)},this.visible=function(val){return void 0===val?m_visible:(m_visible=val,m_this.modified(),m_visible?m_this._bindMouseHandlers():m_this._unbindMouseHandlers(),m_this)},this.bin=function(val){return void 0===val?m_bin:(m_bin=val,m_this.modified(),m_this)},this.dataTime=function(val){return void 0===val?m_dataTime:(m_dataTime=val,m_this.modified(),m_this)},this.buildTime=function(val){return void 0===val?m_buildTime:(m_buildTime=val,m_this.modified(),m_this)},this.updateTime=function(val){return void 0===val?m_updateTime:(m_updateTime=val,m_this.modified(),m_this)},this.data=function(data){return void 0===data?m_this.style("data")||[]:(m_this.style("data",data),m_this.dataTime().modified(),m_this.modified(),m_this)},this.selectionAPI=function(){return m_selectionAPI},this._init=function(arg){if(!m_layer)throw"Feature requires a valid layer";m_style=$.extend({},{opacity:1},void 0===arg.style?{}:arg.style),m_this._bindMouseHandlers()},this._build=function(){},this._drawables=function(){},this._update=function(){},this._exit=function(){m_this._unbindMouseHandlers(),m_selectedFeatures=[],m_style={},arg={},s_exit()},this._init(arg),this},geo.feature.eventID=0,geo.feature.create=function(layer,spec){"use strict";var type=spec.type;if(!layer instanceof geo.layer)return console.warn("Invalid layer"),null;if("object"!=typeof spec)return console.warn("Invalid spec"),null;var feature=layer.createFeature(type);return feature?(spec=spec||{},spec.data=spec.data||[],feature.style(spec)):(console.warn("Could not create feature type '"+type+"'"),null)},inherit(geo.feature,geo.sceneObject),geo.pointFeature=function(arg){"use strict";if(!(this instanceof geo.pointFeature))return new geo.pointFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init,m_rangeTree=null,m_rangeTreeTime=geo.timestamp(),s_data=this.data,m_maxRadius=0,m_clustering=arg.clustering,m_clusterTree=null,m_allData=[],m_lastZoom=null,m_ignoreData=!1;return this.clustering=function(val){return void 0===val?m_clustering:(m_clustering&&!val?(m_clusterTree=null,m_clustering=!1,s_data(m_allData),m_allData=null):!m_clustering&&val&&(m_clustering=!0,m_this._clusterData()),m_this)},this._clusterData=function(){if(m_clustering){var opts=m_clustering===!0?{radius:.01}:m_clustering,position=m_this.position();m_clusterTree=new geo.util.ClusterGroup(opts,this.layer().width(),this.layer().height()),m_allData.forEach(function(d,i){var pt=geo.util.normalizeCoordinates(position(d,i));pt.index=i,m_clusterTree.addPoint(pt)}),m_lastZoom=null,m_this._handleZoom(m_this.layer().map().zoom())}},this._handleZoom=function(zoom){var z=Math.floor(zoom);if(m_clustering&&z!==m_lastZoom){m_lastZoom=z;var data=m_clusterTree.points(z).map(function(d){return m_allData[d.index]});m_clusterTree.clusters(z).forEach(function(d){d.__cluster=!0,d.__data=[],d.obj.each(function(e){d.__data.push(m_allData[e.index])}),data.push(d)}),m_ignoreData=!0,m_this.data(data),m_this.layer().map().draw()}},this.position=function(val){return void 0===val?m_this.style("position"):(val=geo.util.ensureFunction(val),m_this.style("position",function(d,i){return d.__cluster?d:val(d,i)}),m_this.dataTime().modified(),m_this.modified(),m_this)},this._updateRangeTree=function(){if(!(m_rangeTreeTime.getMTime()>=m_this.dataTime().getMTime())){var pts,position,radius=m_this.style.get("radius"),stroke=m_this.style.get("stroke"),strokeWidth=m_this.style.get("strokeWidth");position=m_this.position(),m_maxRadius=0,pts=m_this.data().map(function(d,i){var pt=position(d);return pt.idx=i,m_maxRadius=Math.max(m_maxRadius,radius(d,i)+(stroke(d,i)?strokeWidth(d,i):0)),pt}),m_rangeTree=new geo.util.RangeTree(pts),
-m_rangeTreeTime.modified()}},this.pointSearch=function(p){var min,max,data,box,map,pt,idx=[],found=[],ifound=[],stroke=m_this.style.get("stroke"),strokeWidth=m_this.style.get("strokeWidth"),radius=m_this.style.get("radius");return m_this.selectionAPI()?(data=m_this.data(),data&&data.length?(map=m_this.layer().map(),pt=map.gcsToDisplay(p),min=map.displayToGcs({x:pt.x-m_maxRadius,y:pt.y+m_maxRadius}),max=map.displayToGcs({x:pt.x+m_maxRadius,y:pt.y-m_maxRadius}),box=new geo.util.Box(geo.util.vect(min.x,min.y),geo.util.vect(max.x,max.y)),m_this._updateRangeTree(),m_rangeTree.search(box).forEach(function(q){idx.push(q.idx)}),idx.forEach(function(i){var dx,dy,rad,d=data[i],p=m_this.position()(d,i);rad=radius(data[i],i),rad+=stroke(data[i],i)?strokeWidth(data[i],i):0,p=map.gcsToDisplay(p),dx=p.x-pt.x,dy=p.y-pt.y,Math.sqrt(dx*dx+dy*dy)<=rad&&(found.push(d),ifound.push(i))}),{data:found,index:ifound}):{found:[],index:[]}):[]},this.boxSearch=function(lowerLeft,upperRight){var pos=m_this.position(),idx=[];return m_this.data().forEach(function(d,i){var p=pos(d);p.x>=lowerLeft.x&&p.x<=upperRight.x&&p.y>=lowerLeft.y&&p.y<=upperRight.y&&idx.push(i)}),idx},this.data=function(data){return void 0===data?s_data():(m_clustering&&!m_ignoreData?(m_allData=data,m_this._clusterData()):s_data(data),m_ignoreData=!1,m_this)},this._boundingBox=function(d){var pt,radius;return pt=m_this.position()(d),pt=m_this.layer().map().gcsToDisplay(pt),radius=m_this.style().radius(d),{min:{x:pt.x-radius,y:pt.y-radius},max:{x:pt.x+radius,y:pt.y+radius}}},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{radius:5,stroke:!0,strokeColor:{r:.851,g:.604,b:0},strokeWidth:1.25,strokeOpacity:1,fillColor:{r:1,g:.839,b:.439},fill:!0,fillOpacity:.8,sprites:!1,sprites_image:null,position:function(d){return d}},void 0===arg.style?{}:arg.style);void 0!==arg.position&&(defaultStyle.position=arg.position),m_this.style(defaultStyle),m_this.dataTime().modified(),m_this.geoOn(geo.event.zoom,function(evt){m_this._handleZoom(evt.zoomLevel)})},m_this},geo.event.pointFeature=$.extend({},geo.event.feature),geo.pointFeature.create=function(layer,renderer,spec){"use strict";return spec.type="point",geo.feature.create(layer,spec)},inherit(geo.pointFeature,geo.feature),geo.lineFeature=function(arg){"use strict";if(!(this instanceof geo.lineFeature))return new geo.lineFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init;return this.line=function(val){return void 0===val?m_this.style("line"):(m_this.style("line",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.position=function(val){return void 0===val?m_this.style("position"):(m_this.style("position",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.pointSearch=function(p){function lineDist2(q,u,v){var t,l2=dist2(u,v);return 1>l2?dist2(q,u):(t=((q.x-u.x)*(v.x-u.x)+(q.y-u.y)*(v.y-u.y))/l2,0>t?dist2(q,u):t>1?dist2(q,v):dist2(q,{x:u.x+t*(v.x-u.x),y:u.y+t*(v.y-u.y)}))}function dist2(u,v){var dx=u.x-v.x,dy=u.y-v.y;return dx*dx+dy*dy}var data,pt,map,line,width,pos,indices=[],found=[];return data=m_this.data(),data&&data.length?(map=m_this.layer().map(),line=m_this.line(),width=m_this.style.get("strokeWidth"),pos=m_this.position(),pt=map.gcsToDisplay(p),data.forEach(function(d,index){var last=null;try{line(d,index).forEach(function(current,j){var p=pos(current,j,d,index),s=map.gcsToDisplay(p),r=Math.ceil(width(p,j,d,index)/2)+2;if(r*=r,last&&lineDist2(pt,s,last)<=r)throw"found";last=s})}catch(err){if("found"!==err)throw err;found.push(d),indices.push(index)}}),{data:found,index:indices}):{found:[],index:[]}},this.boxSearch=function(lowerLeft,upperRight,opts){var pos=m_this.position(),idx=[],line=m_this.line();if(opts=opts||{},opts.partial=opts.partial||!1,opts.partial)throw"Unimplemented query method.";return m_this.data().forEach(function(d,i){var inside=!0;line(d,i).forEach(function(e,j){if(inside){var p=pos(e,j,d,i);p.x>=lowerLeft.x&&p.x<=upperRight.x&&p.y>=lowerLeft.y&&p.y<=upperRight.y||(inside=!1)}}),inside&&idx.push(i)}),idx},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeWidth:1,strokeColor:{r:1,g:.8431372549,b:0},strokeStyle:"solid",strokeOpacity:1,line:function(d){return d},position:function(d){return d}},void 0===arg.style?{}:arg.style);void 0!==arg.line&&(defaultStyle.line=arg.line),void 0!==arg.position&&(defaultStyle.position=arg.position),m_this.style(defaultStyle),m_this.dataTime().modified()},this._init(arg),this},geo.lineFeature.create=function(layer,spec){"use strict";return spec.type="line",geo.feature.create(layer,spec)},inherit(geo.lineFeature,geo.feature),geo.pathFeature=function(arg){"use strict";if(!(this instanceof geo.pathFeature))return new geo.pathFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,m_position=void 0===arg.position?[]:arg.position,s_init=this._init;return this.position=function(val){return void 0===val?m_position:(m_position=val,m_this.dataTime().modified(),m_this.modified(),m_this)},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeWidth:function(){return 1},strokeColor:function(){return{r:1,g:1,b:1}}},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_position&&m_this.dataTime().modified()},this._init(arg),this},inherit(geo.pathFeature,geo.feature),geo.polygonFeature=function(arg){"use strict";function getCoordinates(){var posFunc=m_this.position(),polyFunc=m_this.polygon();m_coordinates=m_this.data().map(function(d,i){var outer,inner,poly=polyFunc(d);return outer=(poly.outer||[]).map(function(d0,j){return posFunc.call(m_this,d0,j,d,i)}),inner=(poly.inner||[]).map(function(hole){return(hole||[]).map(function(d0,k){return posFunc.call(m_this,d0,k,d,i)})}),{outer:outer,inner:inner}})}if(!(this instanceof geo.polygonFeature))return new geo.polygonFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_position,m_polygon,m_this=this,s_init=this._init,s_data=this.data,m_coordinates={outer:[],inner:[]};return m_polygon=void 0===arg.polygon?function(d){return d}:arg.polygon,m_position=void 0===arg.position?function(d){return d}:arg.position,this.data=function(arg){var ret=s_data(arg);return void 0!==arg&&getCoordinates(),ret},this.polygon=function(val){return void 0===val?m_polygon:(m_polygon=val,m_this.dataTime().modified(),m_this.modified(),getCoordinates(),m_this)},this.position=function(val){return void 0===val?m_position:(m_position=val,m_this.dataTime().modified(),m_this.modified(),getCoordinates(),m_this)},this.pointSearch=function(coordinate){var found=[],indices=[],data=m_this.data();return m_coordinates.forEach(function(coord,i){var inside=geo.util.pointInPolygon(coordinate,coord.outer,coord.inner);inside&&(indices.push(i),found.push(data[i]))}),{index:indices,found:found}},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{fillColor:{r:0,g:.5,b:.5},fillOpacity:1},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_position&&m_this.dataTime().modified()},this._init(arg),this},inherit(geo.polygonFeature,geo.feature),geo.planeFeature=function(arg){"use strict";if(!(this instanceof geo.planeFeature))return new geo.planeFeature(arg);arg=arg||{},arg.ul=void 0===arg.ul?[0,1,0]:arg.ul,arg.lr=void 0===arg.lr?[1,0,0]:arg.lr,arg.depth=void 0===arg.depth?0:arg.depth,geo.polygonFeature.call(this,arg);var m_this=this,m_origin=[arg.ul.x,arg.lr.y,arg.depth],m_upperLeft=[arg.ul.x,arg.ul.y,arg.depth],m_lowerRight=[arg.lr.x,arg.lr.y,arg.depth],m_defaultDepth=arg.depth,m_drawOnAsyncResourceLoad=void 0===arg.drawOnAsyncResourceLoad?!0:!1,s_init=this._init;return this.origin=function(val){if(void 0===val)return m_origin;if(val instanceof Array){if(val.length>3||val.length<2)throw"Origin point requires point in 2 or 3 dimension";m_origin=val.slice(0),2===m_origin.length&&(m_origin[2]=m_defaultDepth)}return m_this.dataTime().modified(),m_this.modified(),m_this},this.upperLeft=function(val){if(void 0===val)return m_upperLeft;if(val instanceof Array){if(val.length>3||val.length<2)throw"Upper left point requires point in 2 or 3 dimension";m_upperLeft=val.slice(0),2===m_upperLeft.length&&(m_upperLeft[2]=m_defaultDepth)}return m_this.dataTime().modified(),m_this.modified(),m_this},this.lowerRight=function(val){if(void 0===val)return m_lowerRight;if(val instanceof Array){if(val.length>3||val.length<2)throw"Lower right point requires point in 2 or 3 dimension";m_lowerRight=val.slice(0),2===m_lowerRight.length&&(m_lowerRight[2]=m_defaultDepth),m_this.dataTime().modified()}return m_this.dataTime().modified(),m_this.modified(),m_this},this.drawOnAsyncResourceLoad=function(val){return void 0===val?m_drawOnAsyncResourceLoad:(m_drawOnAsyncResourceLoad=val,m_this)},this._init=function(arg){var style=null;s_init.call(m_this,arg),style=m_this.style(),void 0===style.image&&(style.image=null),m_this.style(style)},this._init(arg),this},inherit(geo.planeFeature,geo.polygonFeature),geo.vectorFeature=function(arg){"use strict";if(!(this instanceof geo.vectorFeature))return new geo.vectorFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init,s_style=this.style;this.origin=function(val){return void 0===val?s_style("origin"):(s_style("origin",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.delta=function(val){return void 0===val?s_style("delta"):(s_style("delta",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeColor:"black",strokeWidth:2,strokeOpacity:1,origin:{x:0,y:0,z:0},delta:function(d){return d},scale:null},void 0===arg.style?{}:arg.style);void 0!==arg.origin&&(defaultStyle.origin=arg.origin),m_this.style(defaultStyle),m_this.dataTime().modified()}},inherit(geo.vectorFeature,geo.feature),geo.geomFeature=function(arg){"use strict";return this instanceof geo.geomFeature?(arg=arg||{},geo.feature.call(this,arg),arg.style=void 0===arg.style?$.extend({},{color:[1,1,1],point_sprites:!1,point_sprites_image:null},arg.style):arg.style,this.style(arg.style),this):new geo.geomFeature(arg)},inherit(geo.geomFeature,geo.feature),geo.graphFeature=function(arg){"use strict";if(!(this instanceof geo.graphFeature))return new geo.graphFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_draw=this.draw,s_style=this.style,m_nodes=null,m_points=null,m_children=function(d){return d.children},m_links=[],s_init=this._init,s_exit=this._exit;return this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend(!0,{},{nodes:{radius:5,fill:!0,fillColor:{r:1,g:0,b:0},strokeColor:{r:0,g:0,b:0}},links:{strokeColor:{r:0,g:0,b:0}},linkType:"path"},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_this.nodes(function(d){return d})},this._build=function(){m_this.children().forEach(function(child){child._build()})},this._update=function(){m_this.children().forEach(function(child){child._update()})},this._exit=function(){return m_this.data([]),m_links.forEach(function(link){link._exit(),m_this.removeChild(link)}),m_links=[],m_points._exit(),m_this.removeChild(m_points),s_exit(),m_this},this.style=function(arg,arg2){var out=s_style.call(m_this,arg,arg2);return out!==m_this?out:(m_points.style(arg.nodes),m_links.forEach(function(l){l.style(arg.links)}),m_this)},this.links=function(arg){return void 0===arg?m_children:(m_children=geo.util.ensureFunction(arg),m_this)},this.nodes=function(val){return void 0===val?m_nodes:(m_nodes=val,m_this.modified(),m_this)},this.nodeFeature=function(){return m_points},this.linkFeatures=function(){return m_links},this.draw=function(){var style,layer=m_this.layer(),data=m_this.data(),nLinks=0;return style=m_this.style(),m_points.data(data),m_points.style(style.nodes),data.forEach(function(source){(source.children||[]).forEach(function(target){var link;nLinks+=1,m_links.lengthdata.length&&(gridH=Math.floor(data.length)/gridW),usePos=null===x0||void 0===x0||null===y0||void 0===y0||!dx||!dy,!usePos&&result.wrapLongitude&&(-180>x0||x0>180||-180>x0+dx*(gridW-1)||x0+dx*(gridW-1)>180)&&dx>-180&&180>dx){for(calcX=[],i=0;gridW>i;i+=1){for(x=x0+i*dx;-180>x;)x+=360;for(;x>180;)x-=360;i&&Math.abs(x-calcX[calcX.length-1])>180&&(x>calcX[calcX.length-1]?(calcX.push(x-360),calcX.push(calcX[calcX.length-2]+360)):(calcX.push(x+360),calcX.push(calcX[calcX.length-2]-360)),skipColumn=i),calcX.push(x)}if(gridW+=2,Math.abs(Math.abs(gridWorig*dx)-360)<.01){for(gridW+=1,x=x0+gridWorig*dx;-180>x;)x+=360;for(;x>180;)x-=360;calcX.push(x)}}for(numPts=gridW*gridH,i=0;numPts>i;i+=1)void 0===skipColumn?val=parseFloat(valueFunc(data[i])):(j=Math.floor(i/gridW),origI=i-j*gridW,origI+=origI>skipColumn?-2:0,origI>=gridWorig&&(origI-=gridWorig),origI+=j*gridWorig,val=parseFloat(valueFunc(data[origI]))),values[i]=isNaN(val)?null:val,null!==values[i]&&(idxMap[i]=usedPts,usedPts+=1,void 0===minval&&(minval=maxval=values[i]),values[i]maxval&&(maxval=values[i]));if(!usedPts)return result;if($.isNumeric(result.minValue)||(result.minValue=minval),$.isNumeric(result.maxValue)||(result.maxValue=maxval),rangeValues&&rangeValues.length===result.colorMap.length+1||(rangeValues=null),rangeValues)for(k=1;krangeValues[k+1]){rangeValues=null;break}for(rangeValues&&(result.minValue=rangeValues[0],result.maxValue=rangeValues[rangeValues.length-1]),range=result.maxValue-result.minValue,range||(result.colorMap=result.colorMap.slice(0,1),range=1,rangeValues=null),result.rangeValues=rangeValues,result.factor=result.colorMap.length/range,j=idx=0;gridH-1>j;j+=1,idx+=1)for(i=0;gridW-1>i;i+=1,idx+=1)null!==values[idx]&&null!==values[idx+1]&&null!==values[idx+gridW]&&null!==values[idx+gridW+1]&&i!==skipColumn&&(result.elements.push(idxMap[idx]),result.elements.push(idxMap[idx+1]),result.elements.push(idxMap[idx+gridW]),result.elements.push(idxMap[idx+gridW+1]),result.elements.push(idxMap[idx+gridW]),result.elements.push(idxMap[idx+1]));for(result.pos=new Array(3*usedPts),result.value=new Array(usedPts),result.opacity=new Array(usedPts),j=i=i3=0;numPts>j;j+=1)if(val=values[j],null!==val){if(item=data[j],usePos?(posVal=posFunc(item),result.pos[i3]=posVal.x,result.pos[i3+1]=posVal.y,result.pos[i3+2]=posVal.z||0):(void 0===skipColumn?result.pos[i3]=x0+dx*(j%gridW):result.pos[i3]=calcX[j%gridW],result.pos[i3+1]=y0+dy*Math.floor(j/gridW),result.pos[i3+2]=0),result.opacity[i]=opacityFunc(item),rangeValues&&val>=result.minValue&&val<=result.maxValue){for(k=1;ki;i+=pointOffset)yCoord=inPos[i+1],yCoord>85.0511&&(yCoord=85.0511),-85.0511>yCoord&&(yCoord=-85.0511),outPos[i+1]=geo.mercator.lat2y(yCoord);return inplace&&(feature.positions(outPos),feature.gcs(destGcs)),outPos}return null}},geo.transform.transformFeature=function(destGcs,feature,inplace){"use strict";if(!feature)throw"Invalid (null) feature";if(!(feature instanceof geo.pointFeature||feature instanceof geo.lineFeature))throw"Supports only point or line feature";if(feature.gcs()===destGcs)return feature.positions();if("EPSG:3857"===destGcs)return geo.transform.osmTransformFeature(destGcs,feature,inplace);var i,noOfComponents=null,pointOffset=0,count=null,inPos=null,outPos=null,projPoint=null,srcGcs=feature.gcs(),projSrcGcs=new proj4.Proj(srcGcs),projDestGcs=new proj4.Proj(destGcs);if(inplace=!!inplace,feature instanceof geo.pointFeature||feature instanceof geo.lineFeature){if(inPos=feature.positions(),count=inPos.length,!(inPos instanceof Array))throw"Supports Array of 2D and 3D points";if(noOfComponents=count%2===0?2:count%3===0?3:null,pointOffset=noOfComponents,2!==noOfComponents&&3!==noOfComponents)throw"Transform points require points in 2D or 3D";for(inplace?outPos=inPos:(outPos=[],outPos.length=inPos.length),i=0;count>i;i+=pointOffset)projPoint=2===noOfComponents?new proj4.Point(inPos[i],inPos[i+1],0):new proj4.Point(inPos[i],inPos[i+1],inPos[i+2]),proj4.transform(projSrcGcs,projDestGcs,projPoint),2===noOfComponents?(outPos[i]=projPoint.x,outPos[i+1]=projPoint.y):(outPos[i]=projPoint.x,outPos[i+1]=projPoint.y,outPos[i+2]=projPoint.z);return inplace&&(feature.positions(outPos),feature.gcs(destGcs)),outPos}return null},geo.transform.transformLayer=function(destGcs,layer,baseLayer){"use strict";var features,count,i;if(!layer)throw"Requires valid layer for tranformation";if(!baseLayer)throw"Requires baseLayer used by the map";if(layer!==baseLayer){if(!(layer instanceof geo.featureLayer))throw"Only feature layer transformation is supported";for(features=layer.features(),count=features.length,i=0,i=0;count>i;i+=1)"EPSG:3857"===destGcs&&baseLayer instanceof geo.osmLayer?geo.transform.osmTransformFeature(destGcs,features[i],!0):geo.transform.transformFeature(destGcs,features[i],!0);layer.gcs(destGcs)}},geo.transform.transformCoordinates=function(srcGcs,destGcs,coordinates,numberOfComponents){"use strict";function handleArrayCoordinates(){if(coordinates[0]instanceof Array)if(2===coordinates[0].length)xAcc=function(index){return coordinates[index][0]},yAcc=function(index){return coordinates[index][1]},writer=function(index,x,y){output[index]=[x,y]};else{if(3!==coordinates[0].length)throw"Invalid coordinates. Requires two or three components per array";xAcc=function(index){return coordinates[index][0]},yAcc=function(index){return coordinates[index][1]},zAcc=function(index){return coordinates[index][2]},writer=function(index,x,y,z){output[index]=[x,y,z]}}else if(2===coordinates.length)offset=2,xAcc=function(index){return coordinates[index*offset]},yAcc=function(index){return coordinates[index*offset+1]},writer=function(index,x,y){output[index]=x,output[index+1]=y};else if(3===coordinates.length)offset=3,xAcc=function(index){return coordinates[index*offset]},yAcc=function(index){return coordinates[index*offset+1]},zAcc=function(index){return coordinates[index*offset+2]},writer=function(index,x,y,z){output[index]=x,output[index+1]=y,output[index+2]=z};else{if(!numberOfComponents)throw"Invalid coordinates";offset=numberOfComponents,xAcc=function(index){return coordinates[index]},yAcc=function(index){return coordinates[index+1]},2===numberOfComponents?writer=function(index,x,y){output[index]=x,output[index+1]=y}:(zAcc=function(index){return coordinates[index+2]},writer=function(index,x,y,z){output[index]=x,output[index+1]=y,output[index+2]=z})}}function handleObjectCoordinates(){if(coordinates[0]&&"x"in coordinates[0]&&"y"in coordinates[0])xAcc=function(index){return coordinates[index].x},yAcc=function(index){return coordinates[index].y},"z"in coordinates[0]?(zAcc=function(index){return coordinates[index].z},writer=function(index,x,y,z){output[i]={x:x,y:y,z:z}}):writer=function(index,x,y){output[index]={x:x,y:y}};else{if(!(coordinates&&"x"in coordinates&&"y"in coordinates))throw"Invalid coordinates";xAcc=function(){return coordinates.x},yAcc=function(){return coordinates.y},"z"in coordinates?(zAcc=function(){return coordinates.z},writer=function(index,x,y,z){output={x:x,y:y,z:z}}):writer=function(index,x,y){output={x:x,y:y}}}}var i,count,offset,xCoord,yCoord,zCoord,xAcc,yAcc,zAcc,writer,output,projPoint,projSrcGcs=new proj4.Proj(srcGcs),projDestGcs=new proj4.Proj(destGcs);if(zAcc=function(){return 0},destGcs===srcGcs)return coordinates;if(!destGcs||!srcGcs)throw"Invalid source or destination GCS";if(coordinates instanceof Array)output=[],output.length=coordinates.length,count=coordinates.length,coordinates[0]instanceof Array||coordinates[0]instanceof Object?(offset=1,coordinates[0]instanceof Array?handleArrayCoordinates():coordinates[0]instanceof Object&&handleObjectCoordinates()):handleArrayCoordinates();else if(coordinates&&coordinates instanceof Object){if(count=1,offset=1,!(coordinates&&"x"in coordinates&&"y"in coordinates))throw"Coordinates are not valid";handleObjectCoordinates()}if("EPSG:3857"===destGcs&&"EPSG:4326"===srcGcs){for(i=0;count>i;i+=offset)xCoord=xAcc(i),yCoord=yAcc(i),zCoord=zAcc(i),yCoord>85.0511&&(yCoord=85.0511),-85.0511>yCoord&&(yCoord=-85.0511),writer(i,xCoord,geo.mercator.lat2y(yCoord),zCoord);return output}for(i=0;count>i;i+=offset)return projPoint=new proj4.Point(xAcc(i),yAcc(i),zAcc(i)),proj4.transform(projSrcGcs,projDestGcs,projPoint),writer(i,projPoint.x,projPoint.y,projPoint.z),output},geo.renderer=function(arg){"use strict";if(!(this instanceof geo.renderer))return new geo.renderer(arg);geo.object.call(this),arg=arg||{};var m_this=this,m_layer=void 0===arg.layer?null:arg.layer,m_canvas=void 0===arg.canvas?null:arg.canvas,m_initialized=!1;return this.layer=function(){return m_layer},this.canvas=function(val){return void 0===val?m_canvas:(m_canvas=val,void m_this.modified())},this.map=function(){return m_layer?m_layer.map():null},this.baseLayer=function(){return m_this.map()?m_this.map().baseLayer():void 0},this.initialized=function(val){return void 0===val?m_initialized:(m_initialized=val,m_this)},this.api=function(){throw"Should be implemented by derivied classes"},this.reset=function(){return!0},this.worldToGcs=function(){throw"Should be implemented by derivied classes"},this.displayToGcs=function(){throw"Should be implemented by derivied classes"},this.gcsToDisplay=function(){throw"Should be implemented by derivied classes"},this.worldToDisplay=function(){throw"Should be implemented by derivied classes"},this.displayToWorld=function(){throw"Should be implemented by derivied classes"},this.relMouseCoords=function(event){var totalOffsetX=0,totalOffsetY=0,canvasX=0,canvasY=0,currentElement=m_this.canvas();do totalOffsetX+=currentElement.offsetLeft-currentElement.scrollLeft,totalOffsetY+=currentElement.offsetTop-currentElement.scrollTop,currentElement=currentElement.offsetParent;while(currentElement);return canvasX=event.pageX-totalOffsetX,canvasY=event.pageY-totalOffsetY,{x:canvasX,y:canvasY}},this._init=function(){},this._resize=function(){},this._render=function(){},this},inherit(geo.renderer,geo.object),geo.osmLayer=function(arg){"use strict";function isTileVisible(tile){return tile.zoom in m_visibleTilesRange&&tile.index_x>=m_visibleTilesRange[tile.zoom].startX&&tile.index_x<=m_visibleTilesRange[tile.zoom].endX&&tile.index_y>=m_visibleTilesRange[tile.zoom].startY&&tile.index_y<=m_visibleTilesRange[tile.zoom].endY?!0:!1}function drawTiles(){m_this._removeTiles(),m_this.draw(),delete m_pendingNewTilesStat[m_updateTimerId]}function updateOSMTiles(request){void 0===request&&(request={}),m_zoom||(m_zoom=m_this.map().zoom()),m_lastVisibleZoom||(m_lastVisibleZoom=m_zoom),m_this._addTiles(request),m_lastVisibleZoom!==m_zoom&&(m_lastVisibleZoom=m_zoom),m_this.updateTime().modified()}if(!(this instanceof geo.osmLayer))return new geo.osmLayer(arg);arg=arg||{},arg&&void 0===arg.attribution&&(arg.attribution='© OpenStreetMap contributors'),geo.featureLayer.call(this,arg);var m_tileUrl,m_tileUrlFromTemplate,m_this=this,s_exit=this._exit,m_tiles={},m_hiddenBinNumber=-1,m_lastVisibleBinNumber=-1,m_visibleBinNumber=1e3,m_pendingNewTiles=[],m_pendingInactiveTiles=[],m_numberOfCachedTiles=0,m_tileCacheSize=100,m_baseUrl="http://tile.openstreetmap.org/",m_mapOpacity=1,m_imageFormat="png",m_updateTimerId=null,m_lastVisibleZoom=null,m_visibleTilesRange={},s_init=this._init,m_pendingNewTilesStat={},s_update=this._update,m_updateDefer=null,m_zoom=null,m_crossOrigin="anonymous";return arg&&void 0!==arg.baseUrl&&(m_baseUrl=arg.baseUrl),"/"!==m_baseUrl.charAt(m_baseUrl.length-1)&&(m_baseUrl+="/"),arg&&void 0!==arg.mapOpacity&&(m_mapOpacity=arg.mapOpacity),arg&&void 0!==arg.imageFormat&&(m_imageFormat=arg.imageFormat),arg&&void 0!==arg.displayLast&&arg.displayLast&&(m_lastVisibleBinNumber=999),arg&&void 0!==arg.useCredentials&&arg.useCredentials&&(m_crossOrigin="use-credentials"),m_tileUrl=function(zoom,x,y){return m_baseUrl+zoom+"/"+x+"/"+y+"."+m_imageFormat},m_tileUrlFromTemplate=function(base){return function(zoom,x,y){return base.replace("",zoom).replace("",x).replace("",y)}},this.tileCacheSize=function(val){return void 0===val?m_tileCacheSize:(m_tileCacheSize=val,void m_this.modified())},this.tileUrl=function(val){return void 0===val?m_tileUrl:(m_tileUrl="string"==typeof val?m_tileUrlFromTemplate(val):val,m_this.modified(),m_this)},this.toLocal=function(input){var i,output,delta;if(input instanceof Array&&input.length>0)if(output=[],output.length=input.length,input[0]instanceof Array)if(delta=input%3===0?3:2,2===delta)for(i=0;i=2&&(output=input.slice(0),output[1]=geo.mercator.lat2y(input[1]));else output={},output.x=input.x,output.y=geo.mercator.lat2y(input.y);return output},this.fromLocal=function(input){var i,output;if(input instanceof Array&&input.length>0)if(output=[],output.length=input.length,input[0]instanceof Object)for(i=0;im_tileCacheSize&&i=m_pendingNewTilesStat[m_updateTimerId].total&&drawTiles(),defer.resolve())}}var feature,lastStartX,lastStartY,lastEndX,lastEndY,currStartX,currStartY,currEndX,currEndY,distWorldDeltaPerTile,ren=m_this.renderer(),llx=0,lly=m_this.height(),urx=m_this.width(),ury=0,temp=null,tile=null,tile1x=null,tile1y=null,tile2x=null,tile2y=null,invJ=null,i=0,j=0,worldPt1=ren.displayToWorld([llx,lly]),worldPt2=ren.displayToWorld([urx,ury]),worldDeltaY=null,displayDeltaY=null,worldDelta=null,displayDelta=null,noOfTilesRequired=null,worldDeltaPerTile=null,minDistWorldDeltaPerTile=null;for(worldPt1[0]=Math.max(worldPt1[0],-180),worldPt1[0]=Math.min(worldPt1[0],180),worldPt1[1]=Math.max(worldPt1[1],-180),worldPt1[1]=Math.min(worldPt1[1],180),worldPt2[0]=Math.max(worldPt2[0],-180),worldPt2[0]=Math.min(worldPt2[0],180),worldPt2[1]=Math.max(worldPt2[1],-180),worldPt2[1]=Math.min(worldPt2[1],180),worldDelta=Math.abs(worldPt2[0]-worldPt1[0]),worldDeltaY=Math.abs(worldPt2[1]-worldPt1[1]),displayDelta=urx-llx,displayDeltaY=lly-ury,displayDeltaY>displayDelta&&(displayDelta=displayDeltaY,worldDelta=worldDeltaY),noOfTilesRequired=Math.round(displayDelta/256),worldDeltaPerTile=worldDelta/noOfTilesRequired,minDistWorldDeltaPerTile=Number.POSITIVE_INFINITY,i=20;i>=2;i-=1)distWorldDeltaPerTile=Math.abs(360/Math.pow(2,i)-worldDeltaPerTile),minDistWorldDeltaPerTile>distWorldDeltaPerTile&&(minDistWorldDeltaPerTile=distWorldDeltaPerTile,m_zoom=i);for(tile1x=geo.mercator.long2tilex(worldPt1[0],m_zoom),tile1y=geo.mercator.lat2tiley(worldPt1[1],m_zoom),tile2x=geo.mercator.long2tilex(worldPt2[0],m_zoom),tile2y=geo.mercator.lat2tiley(worldPt2[1],m_zoom),tile1x=Math.max(tile1x,0),tile1x=Math.min(Math.pow(2,m_zoom)-1,tile1x),tile1y=Math.max(tile1y,0),tile1y=Math.min(Math.pow(2,m_zoom)-1,tile1y),tile2x=Math.max(tile2x,0),tile2x=Math.min(Math.pow(2,m_zoom)-1,tile2x),tile2y=Math.max(tile2y,0),tile2y=Math.min(Math.pow(2,m_zoom)-1,tile2y),tile1x>tile2x&&(temp=tile1x,tile1x=tile2x,tile2x=temp),tile2y>tile1y&&(temp=tile1y,tile1y=tile2y,tile2y=temp),currStartX=tile1x,currEndX=tile2x,currStartY=Math.pow(2,m_zoom)-1-tile1y,currEndY=Math.pow(2,m_zoom)-1-tile2y,currStartY>currEndY&&(temp=currStartY,currStartY=currEndY,currEndY=temp),lastStartX=geo.mercator.long2tilex(worldPt1[0],m_lastVisibleZoom),lastStartY=geo.mercator.lat2tiley(worldPt1[1],m_lastVisibleZoom),lastEndX=geo.mercator.long2tilex(worldPt2[0],m_lastVisibleZoom),lastEndY=geo.mercator.lat2tiley(worldPt2[1],m_lastVisibleZoom),lastStartY=Math.pow(2,m_lastVisibleZoom)-1-lastStartY,lastEndY=Math.pow(2,m_lastVisibleZoom)-1-lastEndY,lastStartY>lastEndY&&(temp=lastStartY,lastStartY=lastEndY,lastEndY=temp),m_visibleTilesRange={},m_visibleTilesRange[m_zoom]={startX:currStartX,endX:currEndX,startY:currStartY,endY:currEndY},m_visibleTilesRange[m_lastVisibleZoom]={startX:lastStartX,endX:lastEndX,startY:lastStartY,endY:lastEndY},m_pendingNewTilesStat[m_updateTimerId]={total:(tile2x-tile1x+1)*(tile1y-tile2y+1),count:0},i=tile1x;tile2x>=i;i+=1)for(j=tile2y;tile1y>=j;j+=1)invJ=Math.pow(2,m_zoom)-1-j,m_this._hasTile(m_zoom,i,invJ)?(tile=m_tiles[m_zoom][i][invJ],tile.feature.bin(m_visibleBinNumber),tile.LOADED&&m_updateTimerId in m_pendingNewTilesStat&&(m_pendingNewTilesStat[m_updateTimerId].count+=1),tile.feature._update()):tile=m_this._addTile(request,m_zoom,i,invJ),tile.lastused=new Date,tile.updateTimerId=m_updateTimerId;for(i=0;i=m_pendingNewTilesStat[m_updateTimerId].total&&drawTiles()},this._updateTiles=function(request){var defer=$.Deferred();return m_this.addDeferred(defer),null!==m_updateTimerId?(clearTimeout(m_updateTimerId),m_updateDefer.resolve(),m_updateDefer=defer,m_updateTimerId in m_pendingNewTilesStat&&delete m_pendingNewTilesStat[m_updateTimerId],m_updateTimerId=setTimeout(function(){updateOSMTiles(request),m_updateDefer.resolve()},100)):(m_updateDefer=defer,m_updateTimerId=setTimeout(function(){updateOSMTiles(request),m_updateDefer.resolve()},0)),m_this},this._init=function(){return s_init.call(m_this),m_this.gcs("EPSG:3857"),m_this.map().zoomRange({min:0,max:18}),m_this},this._update=function(request){m_this._updateTiles(request),s_update.call(m_this,request)},this.updateBaseUrl=function(baseUrl){if(baseUrl&&"/"!==baseUrl.charAt(m_baseUrl.length-1)&&(baseUrl+="/"),baseUrl!==m_baseUrl){void 0!==baseUrl&&(m_baseUrl=baseUrl);var tile,x,y,zoom;for(zoom in m_tiles)for(x in m_tiles[zoom])for(y in m_tiles[zoom][x])tile=m_tiles[zoom][x][y],tile.INVALID=!0,m_this.deleteFeature(tile.feature);m_tiles={},m_pendingNewTiles=[],m_pendingInactiveTiles=[],m_numberOfCachedTiles=0,m_visibleTilesRange={},m_pendingNewTilesStat={},null!==m_updateTimerId&&(clearTimeout(m_updateTimerId),m_updateTimerId=null),this._update()}},this.mapOpacity=function(val){if(void 0===val)return m_mapOpacity;if(val!==m_mapOpacity){m_mapOpacity=val;var zoom,x,y,tile;for(zoom in m_tiles)for(x in m_tiles[zoom])for(y in m_tiles[zoom][x])tile=m_tiles[zoom][x][y],tile.feature.style().opacity=val,tile.feature._update();m_this.modified()}return m_this},this._exit=function(){m_tiles={},m_pendingNewTiles=[],m_pendingInactiveTiles=[],m_numberOfCachedTiles=0,m_visibleTilesRange={},m_pendingNewTilesStat={},s_exit()},arg&&void 0!==arg.tileUrl&&this.tileUrl(arg.tileUrl),this},inherit(geo.osmLayer,geo.featureLayer),geo.registerLayer("osm",geo.osmLayer),geo.gl={},geo.gl.renderer=function(arg){"use strict";return this instanceof geo.gl.renderer?(geo.renderer.call(this,arg),this.contextRenderer=function(){throw"Should be implemented by derived classes"},this):new geo.gl.renderer(arg)},inherit(geo.gl.renderer,geo.renderer),geo.gl.lineFeature=function(arg){"use strict";function createVertexShader(){var vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute vec3 prev;","attribute vec3 next;","attribute float offset;","attribute vec3 strokeColor;","attribute float strokeOpacity;","attribute float strokeWidth;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float pixelWidth;","uniform float aspect;","varying vec3 strokeColorVar;","varying float strokeWidthVar;","varying float strokeOpacityVar;","void main(void)","{"," if (strokeOpacity < 0.0) {"," gl_Position = vec4(2, 2, 0, 1);"," return;"," }"," const float PI = 3.14159265358979323846264;"," vec4 worldPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);"," if (worldPos.w != 0.0) {"," worldPos = worldPos/worldPos.w;"," }"," vec4 worldNext = projectionMatrix * modelViewMatrix * vec4(next.xyz, 1);"," if (worldNext.w != 0.0) {"," worldNext = worldNext/worldNext.w;"," }"," vec4 worldPrev = projectionMatrix* modelViewMatrix * vec4(prev.xyz, 1);"," if (worldPrev.w != 0.0) {"," worldPrev = worldPrev/worldPrev.w;"," }"," strokeColorVar = strokeColor;"," strokeWidthVar = strokeWidth;"," strokeOpacityVar = strokeOpacity;"," vec2 deltaNext = worldNext.xy - worldPos.xy;"," vec2 deltaPrev = worldPos.xy - worldPrev.xy;"," float angleNext = 0.0, anglePrev = 0.0;"," if (deltaNext.xy != vec2(0.0, 0.0))"," angleNext = atan(deltaNext.y / aspect, deltaNext.x);"," if (deltaPrev.xy == vec2(0.0, 0.0)) anglePrev = angleNext;"," else anglePrev = atan(deltaPrev.y / aspect, deltaPrev.x);"," if (deltaNext.xy == vec2(0.0, 0.0)) angleNext = anglePrev;"," float angle = (anglePrev + angleNext) / 2.0;"," float cosAngle = cos(anglePrev - angle);"," if (cosAngle < 0.1) { cosAngle = sign(cosAngle) * 1.0; angle = 0.0; }"," float distance = (offset * strokeWidth * pixelWidth) /"," cosAngle;"," worldPos.x += distance * sin(angle);"," worldPos.y -= distance * cos(angle) * aspect;"," gl_Position = worldPos;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","varying vec3 strokeColorVar;","varying float strokeWidthVar;","varying float strokeOpacityVar;","void main () {"," gl_FragColor = vec4 (strokeColorVar, strokeOpacityVar);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function createGLLines(){var i,j,k,v,len,lineItem,lineItemData,vertTemp,pos,posIdx3,posBuf,nextBuf,prevBuf,offsetBuf,indicesBuf,strokeWidthBuf,strokeColorBuf,strokeOpacityBuf,dest,dest3,data=m_this.data(),numSegments=0,vert=[{},{}],position=[],posFunc=m_this.position(),strkWidthFunc=m_this.style.get("strokeWidth"),strkColorFunc=m_this.style.get("strokeColor"),strkOpacityFunc=m_this.style.get("strokeOpacity"),order=m_this.featureVertices(),geom=m_mapper.geometryData();for(i=0;i=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_pixelWidthUnif.set(1/m_this.renderer().width()),m_aspectUniform.set(m_this.renderer().width()/m_this.renderer().height()),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.lineFeature,geo.lineFeature),geo.registerFeature("vgl","line",geo.gl.lineFeature),geo.gl.pointFeature=function(arg){"use strict";function createVertexShader(){var shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function pointPolygon(x,y,w,h){var verts;switch(m_primitiveShape){case"triangle":verts=[x,y-2*h,x-w*Math.sqrt(3),y+h,x+w*Math.sqrt(3),y+h];break;case"sprite":verts=[x,y];break;default:verts=[x-w,y+h,x-w,y-h,x+w,y+h,x-w,y-h,x+w,y-h,x+w,y+h]}return verts}function createGLPoints(){var i,j,posBuf,posVal,posFunc,unitBuf,indices,radius,radiusVal,radFunc,stroke,strokeVal,strokeFunc,strokeWidth,strokeWidthVal,strokeWidthFunc,strokeOpacity,strokeOpacityVal,strokeOpactityFunc,strokeColor,strokeColorVal,strokeColorFunc,fill,fillVal,fillFunc,fillOpacity,fillOpacityVal,fillOpacityFunc,fillColor,fillColorVal,fillColorFunc,item,ivpf,ivpf3,iunit,i3,numPts=m_this.data().length,unit=pointPolygon(0,0,1,1),position=new Array(3*numPts),vpf=m_this.verticesPerFeature(),data=m_this.data(),geom=m_mapper.geometryData();for(posFunc=m_this.position(),radFunc=m_this.style.get("radius"),strokeFunc=m_this.style.get("stroke"),strokeWidthFunc=m_this.style.get("strokeWidth"),strokeOpactityFunc=m_this.style.get("strokeOpacity"),strokeColorFunc=m_this.style.get("strokeColor"),fillFunc=m_this.style.get("fill"),fillOpacityFunc=m_this.style.get("fillOpacity"),fillColorFunc=m_this.style.get("fillColor"),i=i3=0;numPts>i;i+=1,i3+=3)posVal=posFunc(data[i]),position[i3]=posVal.x,position[i3+1]=posVal.y,position[i3+2]=posVal.z||0;for(position=geo.transform.transformCoordinates(m_this.gcs(),m_this.layer().map().gcs(),position,3),posBuf=getBuffer(geom,"pos",vpf*numPts*3),"sprite"!==m_primitiveShape&&(unitBuf=getBuffer(geom,"unit",vpf*numPts*2)),radius=getBuffer(geom,"rad",vpf*numPts*1),stroke=getBuffer(geom,"stroke",vpf*numPts*1),strokeWidth=getBuffer(geom,"strokeWidth",vpf*numPts*1),strokeOpacity=getBuffer(geom,"strokeOpacity",vpf*numPts*1),strokeColor=getBuffer(geom,"strokeColor",vpf*numPts*3),fill=getBuffer(geom,"fill",vpf*numPts*1),fillOpacity=getBuffer(geom,"fillOpacity",vpf*numPts*1),fillColor=getBuffer(geom,"fillColor",vpf*numPts*3),indices=geom.primitive(0).indices(),indices instanceof Uint16Array&&indices.length===vpf*numPts||(indices=new Uint16Array(vpf*numPts),geom.primitive(0).setIndices(indices)),i=ivpf=ivpf3=iunit=i3=0;numPts>i;i+=1,i3+=3){if(item=data[i],"sprite"!==m_primitiveShape)for(j=0;jj;j+=1,ivpf+=1,ivpf3+=3)posBuf[ivpf3]=position[i3],posBuf[ivpf3+1]=position[i3+1],posBuf[ivpf3+2]=position[i3+2],radius[ivpf]=radiusVal,stroke[ivpf]=strokeVal,strokeWidth[ivpf]=strokeWidthVal,strokeOpacity[ivpf]=strokeOpacityVal,strokeColor[ivpf3]=strokeColorVal.r,strokeColor[ivpf3+1]=strokeColorVal.g,strokeColor[ivpf3+2]=strokeColorVal.b,fill[ivpf]=fillVal,fillOpacity[ivpf]=fillOpacityVal,fillColor[ivpf3]=fillColorVal.r,fillColor[ivpf3+1]=fillColorVal.g,fillColor[ivpf3+2]=fillColorVal.b}geom.boundsDirty(!0),m_mapper.modified(),m_mapper.boundsDirtyTimestamp().modified()}function getBuffer(geom,srcName,len){var data,src=geom.sourceByName(srcName);return data=src.data(),data instanceof Float32Array&&data.length===len?data:(data=new Float32Array(len),src.setData(data),data)}if(!(this instanceof geo.gl.pointFeature))return new geo.gl.pointFeature(arg);arg=arg||{},geo.pointFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_actor=null,m_mapper=null,m_pixelWidthUniform=null,m_aspectUniform=null,m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,m_primitiveShape="sprite",s_init=this._init,s_update=this._update,vertexShaderSource=null,fragmentShaderSource=null;return("triangle"===arg.primitiveShape||"square"===arg.primitiveShape||"sprite"===arg.primitiveShape)&&(m_primitiveShape=arg.primitiveShape),vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute float rad;","attribute vec3 fillColor;","attribute vec3 strokeColor;","attribute float fillOpacity;","attribute float strokeWidth;","attribute float strokeOpacity;","attribute float fill;","attribute float stroke;","uniform float pixelWidth;","uniform float aspect;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying vec4 fillColorVar;","varying vec4 strokeColorVar;","varying float radiusVar;","varying float strokeWidthVar;","varying float fillVar;","varying float strokeVar;"],"sprite"!==m_primitiveShape&&(vertexShaderSource=vertexShaderSource.concat(["attribute vec2 unit;","varying vec3 unitVar;"])),vertexShaderSource.push.apply(vertexShaderSource,["void main(void)","{"," strokeWidthVar = strokeWidth;"," // No stroke or fill implies nothing to draw"," if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) {"," strokeVar = 0.0;"," strokeWidthVar = 0.0;"," }"," else"," strokeVar = 1.0;"," if (fill < 1.0 || rad <= 0.0 || fillOpacity <= 0.0)"," fillVar = 0.0;"," else"," fillVar = 1.0;"," if (fillVar == 0.0 && strokeVar == 0.0) {"," gl_Position = vec4(2, 2, 0, 1);"," return;"," }"," fillColorVar = vec4 (fillColor, fillOpacity);"," strokeColorVar = vec4 (strokeColor, strokeOpacity);"," radiusVar = rad;"]),"sprite"===m_primitiveShape?vertexShaderSource.push.apply(vertexShaderSource,[" gl_Position = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;"," gl_PointSize = 2.0 * (rad + strokeWidthVar); ","}"]):vertexShaderSource.push.apply(vertexShaderSource,[" unitVar = vec3 (unit, 1.0);"," vec4 p = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;"," if (p.w != 0.0) {"," p = p / p.w;"," }"," p += (rad + strokeWidthVar) * "," vec4 (unit.x * pixelWidth, unit.y * pixelWidth * aspect, 0.0, 1.0);"," gl_Position = vec4(p.xyz, 1.0);","}"]),vertexShaderSource=vertexShaderSource.join("\n"),fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","uniform float aspect;","varying vec4 fillColorVar;","varying vec4 strokeColorVar;","varying float radiusVar;","varying float strokeWidthVar;","varying float fillVar;","varying float strokeVar;"],"sprite"!==m_primitiveShape&&fragmentShaderSource.push("varying vec3 unitVar;"),fragmentShaderSource.push.apply(fragmentShaderSource,["void main () {"," vec4 strokeColor, fillColor;"," float endStep;"," // No stroke or fill implies nothing to draw"," if (fillVar == 0.0 && strokeVar == 0.0)"," discard;"]),"sprite"===m_primitiveShape?fragmentShaderSource.push(" float rad = 2.0 * length (gl_PointCoord - vec2(0.5));"):fragmentShaderSource.push(" float rad = length (unitVar.xy);"),fragmentShaderSource.push.apply(fragmentShaderSource,[" if (rad > 1.0)"," discard;"," // If there is no stroke, the fill region should transition to nothing"," if (strokeVar == 0.0) {"," strokeColor = vec4 (fillColorVar.rgb, 0.0);"," endStep = 1.0;"," } else {"," strokeColor = strokeColorVar;"," endStep = radiusVar / (radiusVar + strokeWidthVar);"," }"," // Likewise, if there is no fill, the stroke should transition to nothing"," if (fillVar == 0.0)"," fillColor = vec4 (strokeColor.rgb, 0.0);"," else"," fillColor = fillColorVar;"," // Distance to antialias over"," float antialiasDist = 3.0 / (2.0 * radiusVar);"," if (rad < endStep) {"," float step = smoothstep (endStep - antialiasDist, endStep, rad);"," gl_FragColor = mix (fillColor, strokeColor, step);"," } else {"," float step = smoothstep (1.0 - antialiasDist, 1.0, rad);"," gl_FragColor = mix (strokeColor, vec4 (strokeColor.rgb, 0.0), step);"," }","}"]),fragmentShaderSource=fragmentShaderSource.join("\n"),this.actors=function(){return m_actor?[m_actor]:[]},this.verticesPerFeature=function(){var unit=pointPolygon(0,0,1,1);return unit.length/2},this._init=function(){var prog=vgl.shaderProgram(),vertexShader=createVertexShader(),fragmentShader=createFragmentShader(),posAttr=vgl.vertexAttribute("pos"),unitAttr=vgl.vertexAttribute("unit"),radAttr=vgl.vertexAttribute("rad"),strokeWidthAttr=vgl.vertexAttribute("strokeWidth"),fillColorAttr=vgl.vertexAttribute("fillColor"),fillAttr=vgl.vertexAttribute("fill"),strokeColorAttr=vgl.vertexAttribute("strokeColor"),strokeAttr=vgl.vertexAttribute("stroke"),fillOpacityAttr=vgl.vertexAttribute("fillOpacity"),strokeOpacityAttr=vgl.vertexAttribute("strokeOpacity"),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),mat=vgl.material(),blend=vgl.blend(),geom=vgl.geometryData(),sourcePositions=vgl.sourceDataP3fv({name:"pos"}),sourceUnits=vgl.sourceDataAnyfv(2,vgl.vertexAttributeKeysIndexed.One,{name:"unit"}),sourceRadius=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Two,{name:"rad"}),sourceStrokeWidth=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Three,{name:"strokeWidth"}),sourceFillColor=vgl.sourceDataAnyfv(3,vgl.vertexAttributeKeysIndexed.Four,{name:"fillColor"}),sourceFill=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Five,{name:"fill"}),sourceStrokeColor=vgl.sourceDataAnyfv(3,vgl.vertexAttributeKeysIndexed.Six,{name:"strokeColor"}),sourceStroke=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Seven,{name:"stroke"}),sourceAlpha=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Eight,{name:"fillOpacity"}),sourceStrokeOpacity=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Nine,{name:"strokeOpacity"}),primitive=new vgl.triangles;"sprite"===m_primitiveShape&&(primitive=new vgl.points),m_pixelWidthUniform=new vgl.floatUniform("pixelWidth",2/m_this.renderer().width()),m_aspectUniform=new vgl.floatUniform("aspect",m_this.renderer().width()/m_this.renderer().height()),s_init.call(m_this,arg),m_mapper=vgl.mapper({dynamicDraw:m_dynamicDraw}),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),"sprite"!==m_primitiveShape&&prog.addVertexAttribute(unitAttr,vgl.vertexAttributeKeysIndexed.One),prog.addVertexAttribute(radAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addVertexAttribute(strokeWidthAttr,vgl.vertexAttributeKeysIndexed.Three),prog.addVertexAttribute(fillColorAttr,vgl.vertexAttributeKeysIndexed.Four),prog.addVertexAttribute(fillAttr,vgl.vertexAttributeKeysIndexed.Five),prog.addVertexAttribute(strokeColorAttr,vgl.vertexAttributeKeysIndexed.Six),prog.addVertexAttribute(strokeAttr,vgl.vertexAttributeKeysIndexed.Seven),prog.addVertexAttribute(fillOpacityAttr,vgl.vertexAttributeKeysIndexed.Eight),prog.addVertexAttribute(strokeOpacityAttr,vgl.vertexAttributeKeysIndexed.Nine),prog.addUniform(m_pixelWidthUniform),prog.addUniform(m_aspectUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),m_actor=vgl.actor(),m_actor.setMaterial(mat),m_actor.setMapper(m_mapper),geom.addSource(sourcePositions),geom.addSource(sourceUnits),geom.addSource(sourceRadius),geom.addSource(sourceStrokeWidth),geom.addSource(sourceFillColor),geom.addSource(sourceFill),geom.addSource(sourceStrokeColor),geom.addSource(sourceStroke),geom.addSource(sourceAlpha),geom.addSource(sourceStrokeOpacity),geom.addPrimitive(primitive),m_mapper.setGeometryData(geom)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLPoints(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.renderer().contextRenderer().render(),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()i;i+=1)buffers.write("pos",position[i],start+i,1),buffers.write("indices",[i],start+i,1),buffers.write("fillColor",fillColorNew[i],start+i,1),buffers.write("fillOpacity",[fillOpacityNew[i]],start+i,1);sourcePositions.pushBack(buffers.get("pos")),geom.addSource(sourcePositions),sourceFillColor.pushBack(buffers.get("fillColor")),geom.addSource(sourceFillColor),sourceFillOpacity.pushBack(buffers.get("fillOpacity")),geom.addSource(sourceFillOpacity),trianglePrimitive.setIndices(buffers.get("indices")),geom.addPrimitive(trianglePrimitive),m_mapper.setGeometryData(geom)}if(!(this instanceof geo.gl.polygonFeature))return new geo.gl.polygonFeature(arg);arg=arg||{},geo.polygonFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_actor=vgl.actor(),m_mapper=vgl.mapper(),m_material=vgl.material(),s_init=this._init,s_update=this._update;return this._init=function(arg){var blend=vgl.blend(),prog=vgl.shaderProgram(),posAttr=vgl.vertexAttribute("pos"),fillColorAttr=vgl.vertexAttribute("fillColor"),fillOpacityAttr=vgl.vertexAttribute("fillOpacity"),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),vertexShader=createVertexShader(),fragmentShader=createFragmentShader();s_init.call(m_this,arg),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(fillColorAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addVertexAttribute(fillOpacityAttr,vgl.vertexAttributeKeysIndexed.Three),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),m_material.addAttribute(prog),m_material.addAttribute(blend),m_actor.setMapper(m_mapper),m_actor.setMaterial(m_material)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLPolygons(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.polygonFeature,geo.polygonFeature),geo.registerFeature("vgl","polygon",geo.gl.polygonFeature),geo.gl.contourFeature=function(arg){"use strict";function createVertexShader(){var vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute float value;","attribute float opacity;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying float valueVar;","varying float opacityVar;","void main(void)","{"," vec4 scrPos = projectionMatrix * modelViewMatrix * vec4(pos.xy, 0, 1);"," if (scrPos.w != 0.0) {"," scrPos = scrPos / scrPos.w;"," }"," valueVar = value;"," opacityVar = opacity;"," gl_Position = scrPos;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","uniform vec4 minColor;","uniform vec4 maxColor;","uniform float steps;","uniform bool stepped;","uniform sampler2D sampler2d;","varying float valueVar;","varying float opacityVar;","void main () {"," vec4 clr;"," if (valueVar < 0.0) {"," clr = minColor;"," } else if (valueVar > steps) {"," clr = maxColor;"," } else {"," float step;"," if (stepped) {"," step = floor(valueVar) + 0.5;"," if (step > steps) {"," step = steps - 0.5;"," }"," } else {"," step = valueVar;"," }"," clr = texture2D(sampler2d, vec2(step / steps, 0.0));"," }"," gl_FragColor = vec4(clr.rgb, clr.a * opacityVar);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function createGLContours(){var i,i3,j,j3,posBuf,opacityBuf,valueBuf,indicesBuf,contour=m_this.createContours(),numPts=contour.elements.length,colorTable=[],geom=m_mapper.geometryData();for(m_minColorUniform.set([contour.minColor.r,contour.minColor.g,contour.minColor.b,contour.minColor.a]),m_maxColorUniform.set([contour.maxColor.r,contour.maxColor.g,contour.maxColor.b,contour.maxColor.a]),m_stepsUniform.set(contour.colorMap.length),m_steppedUniform.set(contour.stepped),i=0;ii;i+=1,i3+=3)j=contour.elements[i],j3=3*j,posBuf[i3]=contour.pos[j3],posBuf[i3+1]=contour.pos[j3+1],posBuf[i3+2]=contour.pos[j3+2],opacityBuf[i]=contour.opacity[j],valueBuf[i]=contour.value[j];indicesBuf=geom.primitive(0).indices(),indicesBuf instanceof Uint16Array&&indicesBuf.length===numPts||(indicesBuf=new Uint16Array(numPts),geom.primitive(0).setIndices(indicesBuf)),geom.boundsDirty(!0),m_mapper.modified(),m_mapper.boundsDirtyTimestamp().modified()}function getBuffer(geom,srcName,len){var data,src=geom.sourceByName(srcName);return data=src.data(),data instanceof Float32Array&&data.length===len?data:(data=new Float32Array(len),src.setData(data),data)}if(!(this instanceof geo.gl.contourFeature))return new geo.gl.contourFeature(arg);arg=arg||{},geo.contourFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_textureUnit=7,m_actor=null,m_mapper=null,m_material=null,m_texture=null,m_minColorUniform=null,m_maxColorUniform=null,m_stepsUniform=null,m_steppedUniform=null,m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,s_init=this._init,s_update=this._update;return this._init=function(arg){var blend=vgl.blend(),prog=vgl.shaderProgram(),mat=vgl.material(),tex=vgl.lookupTable(),geom=vgl.geometryData(),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"sampler2d"),vertexShader=createVertexShader(),fragmentShader=createFragmentShader(),posAttr=vgl.vertexAttribute("pos"),valueAttr=vgl.vertexAttribute("value"),opacityAttr=vgl.vertexAttribute("opacity"),sourcePositions=vgl.sourceDataP3fv({name:"pos"}),sourceValues=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.One,{name:"value"}),sourceOpacity=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Two,{name:"opacity"}),primitive=new vgl.triangles;s_init.call(m_this,arg),m_mapper=vgl.mapper({dynamicDraw:m_dynamicDraw}),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(valueAttr,vgl.vertexAttributeKeysIndexed.One),prog.addVertexAttribute(opacityAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),m_minColorUniform=new vgl.uniform(vgl.GL.FLOAT_VEC4,"minColor"),prog.addUniform(m_minColorUniform),m_maxColorUniform=new vgl.uniform(vgl.GL.FLOAT_VEC4,"maxColor"),prog.addUniform(m_maxColorUniform),m_stepsUniform=new vgl.uniform(vgl.GL.FLOAT,"steps"),prog.addUniform(m_stepsUniform),m_steppedUniform=new vgl.uniform(vgl.GL.BOOL,"stepped"),prog.addUniform(m_steppedUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),prog.addUniform(samplerUniform),tex.setTextureUnit(m_textureUnit),samplerUniform.set(m_textureUnit),m_material=mat,m_material.addAttribute(prog),m_material.addAttribute(blend),m_texture=tex,m_material.addAttribute(m_texture),m_actor=vgl.actor(),m_actor.setMaterial(m_material),m_actor.setMapper(m_mapper),geom.addSource(sourcePositions),geom.addSource(sourceValues),geom.addSource(sourceOpacity),geom.addPrimitive(primitive),m_mapper.setGeometryData(geom)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLContours(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.contourFeature,geo.contourFeature),geo.registerFeature("vgl","contour",geo.gl.contourFeature),geo.gl.vglRenderer=function(arg){"use strict";if(!(this instanceof geo.gl.vglRenderer))return new geo.gl.vglRenderer(arg);arg=arg||{},geo.gl.renderer.call(this,arg);var m_this=this,m_contextRenderer=null,m_viewer=null,m_width=0,m_height=0,m_initialized=!1,s_init=this._init;return this.width=function(){return m_width},this.height=function(){return m_height},this.displayToWorld=function(input){var i,delta,output,temp,point,ren=m_this.contextRenderer(),cam=ren.camera(),fdp=ren.focusDisplayPoint();if(input instanceof Array&&input.length>0)if(output=[],input[0]instanceof Object)for(delta=1,i=0;i0)if(output=[],input[0]instanceof Object)for(delta=1,i=0;i0&&m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length),m_this},this._resize=function(x,y,w,h){var baseContextRenderer,baseCamera,focalPoint,position,zoom,newZ,mapCenter,vglRenderer=m_this.contextRenderer(),map=m_this.layer().map(),camera=vglRenderer.camera(),renderWindow=m_viewer.renderWindow(),layer=m_this.layer(),baseLayer=layer.map().baseLayer();return m_width=w,m_height=h,m_this.canvas().attr("width",w),m_this.canvas().attr("height",h),renderWindow.positionAndResize(x,y,w,h),m_this._render(),baseLayer&&!m_initialized?(m_initialized=!0,vglRenderer&&vglRenderer.camera()||console.log("Zoom event triggered on unconnected vgl renderer."),position=camera.position(),zoom=map.zoom(),newZ=camera.zoomToHeight(zoom,w,h),layer!==baseLayer?(baseContextRenderer=baseLayer.renderer().contextRenderer(),baseCamera=baseContextRenderer.camera(),position=baseCamera.position(),focalPoint=baseCamera.focalPoint(),camera.setPosition(position[0],position[1],position[2]),camera.setFocalPoint(focalPoint[0],focalPoint[1],focalPoint[2])):(mapCenter=layer.toLocal(layer.map().center()),focalPoint=camera.focalPoint(),camera.setPosition(mapCenter.x,mapCenter.y,newZ),camera.setFocalPoint(mapCenter.x,mapCenter.y,focalPoint[2])),camera.setParallelExtents({zoom:zoom}),m_this._updateRendererCamera(),m_this):void 0},this._render=function(){return m_viewer.render(),m_this},this._updateRendererCamera=function(){var pos,fp,cr,pe,vglRenderer=m_this.contextRenderer(),renderWindow=m_viewer.renderWindow(),camera=vglRenderer.camera();vglRenderer.resetCameraClippingRange(),pos=camera.position(),fp=camera.focalPoint(),cr=camera.clippingRange(),pe=camera.parallelExtents(),renderWindow.renderers().forEach(function(renderer){var cam=renderer.camera();cam!==camera&&(cam.setPosition(pos[0],pos[1],pos[2]),cam.setFocalPoint(fp[0],fp[1],fp[2]),cam.setClippingRange(cr[0],cr[1]),cam.setParallelExtents(pe),renderer.render())})},m_this.layer().geoOn(geo.event.pan,function(evt){var camera,focusPoint,centerDisplay,centerGeo,newCenterDisplay,newCenterGeo,renderWindow,vglRenderer=m_this.contextRenderer(),layer=m_this.layer();evt.geo&&evt.geo._triggeredBy!==layer&&(vglRenderer&&vglRenderer.camera()||console.log("Pan event triggered on unconnected VGL renderer."),renderWindow=m_viewer.renderWindow(),camera=vglRenderer.camera(),focusPoint=renderWindow.focusDisplayPoint(),centerDisplay=[m_width/2,m_height/2,0],centerGeo=renderWindow.displayToWorld(centerDisplay[0],centerDisplay[1],focusPoint,vglRenderer),newCenterDisplay=[centerDisplay[0]+evt.screenDelta.x,centerDisplay[1]+evt.screenDelta.y],newCenterGeo=renderWindow.displayToWorld(newCenterDisplay[0],newCenterDisplay[1],focusPoint,vglRenderer),camera.pan(centerGeo[0]-newCenterGeo[0],centerGeo[1]-newCenterGeo[1],centerGeo[2]-newCenterGeo[2]),evt.center={x:newCenterGeo[0],y:newCenterGeo[1],z:newCenterGeo[2]},m_this._updateRendererCamera())}),m_this.layer().geoOn(geo.event.zoom,function(evt){var camera,renderWindow,center,dir,focusPoint,position,newZ,vglRenderer=m_this.contextRenderer(),layer=m_this.layer();if(evt.geo&&evt.geo._triggeredBy!==layer){vglRenderer&&vglRenderer.camera()||console.log("Zoom event triggered on unconnected vgl renderer."),renderWindow=m_viewer.renderWindow(),camera=vglRenderer.camera(),focusPoint=camera.focalPoint(),position=camera.position();var windowSize=renderWindow.windowSize();newZ=camera.zoomToHeight(evt.zoomLevel,windowSize[0],windowSize[1]),evt.pan=null,evt.screenPosition&&(center=renderWindow.displayToWorld(evt.screenPosition.x,evt.screenPosition.y,focusPoint,vglRenderer),dir=[center[0]-position[0],center[1]-position[1],center[2]-position[2]],evt.center=layer.fromLocal({x:position[0]+dir[0]*(1-newZ/position[2]),y:position[1]+dir[1]*(1-newZ/position[2])})),camera.setPosition(position[0],position[1],newZ),camera.setParallelExtents({zoom:evt.zoomLevel}),m_this._updateRendererCamera()}}),m_this.layer().geoOn(geo.event.parallelprojection,function(evt){var camera,vglRenderer=m_this.contextRenderer(),layer=m_this.layer();evt.geo&&evt.geo._triggeredBy!==layer&&(vglRenderer&&vglRenderer.camera()||console.log("Parallel projection event triggered on unconnected VGL renderer."),camera=vglRenderer.camera(),camera.setEnableParallelProjection(evt.parallelProjection),m_this._updateRendererCamera())}),this},inherit(geo.gl.vglRenderer,geo.gl.renderer),geo.registerRenderer("vgl",geo.gl.vglRenderer),geo.d3={},function(){"use strict";var chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",strLength=8;geo.d3.uniqueID=function(){var i,strArray=[];for(strArray.length=strLength,i=0;strLength>i;i+=1)strArray[i]=chars.charAt(Math.floor(Math.random()*chars.length));return strArray.join("")},geo.event.d3Rescale="geo_d3_rescale"}(),geo.d3.object=function(arg){"use strict";if(!(this instanceof geo.object))return new geo.d3.object(arg);geo.sceneObject.call(this);var m_id="d3-"+geo.d3.uniqueID(),s_exit=this._exit,m_this=this,s_draw=this.draw;return this._d3id=function(){return m_id},this.select=function(){return m_this.renderer().select(m_this._d3id())},this.draw=function(){return m_this._update(),s_draw(),m_this},this._exit=function(){m_this.renderer()._removeFeature(m_this._d3id()),s_exit()},this},inherit(geo.d3.object,geo.sceneObject),geo.d3.pointFeature=function(arg){"use strict";if(!(this instanceof geo.d3.pointFeature))return new geo.d3.pointFeature(arg);arg=arg||{},geo.pointFeature.call(this,arg),geo.d3.object.call(this);var m_sticky,m_this=this,s_init=this._init,s_update=this._update,m_buildTime=geo.timestamp(),m_style={};return this._init=function(arg){return s_init.call(m_this,arg),m_sticky=m_this.layer().sticky(),m_this},this._build=function(){var data=m_this.data(),s_style=m_this.style.get(),m_renderer=m_this.renderer(),pos_func=m_this.position();return s_update.call(m_this),data||(data=[]),m_style.id=m_this._d3id(),m_style.data=data,m_style.append="circle",m_style.attributes={r:m_renderer._convertScale(s_style.radius),cx:function(d){return m_renderer.worldToDisplay(pos_func(d)).x},cy:function(d){return m_renderer.worldToDisplay(pos_func(d)).y}},m_style.style=s_style,m_style.classes=["d3PointFeature"],m_this.renderer()._drawFeatures(m_style),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.pointFeature,geo.pointFeature),geo.registerFeature("d3","point",geo.d3.pointFeature),geo.d3.lineFeature=function(arg){"use strict";if(!(this instanceof geo.d3.lineFeature))return new geo.d3.lineFeature(arg);arg=arg||{},geo.lineFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,s_init=this._init,m_buildTime=geo.timestamp(),s_update=this._update;return this._init=function(arg){return s_init.call(m_this,arg),m_this},this._build=function(){var data=m_this.data()||[],s_style=m_this.style(),m_renderer=m_this.renderer(),pos_func=m_this.position(),line=d3.svg.line().x(function(d){return m_renderer.worldToDisplay(d).x}).y(function(d){return m_renderer.worldToDisplay(d).y});return s_update.call(m_this),s_style.fill=function(){return!1},data.forEach(function(item,idx){function wrapStyle(func){return geo.util.isFunction(func)?function(){return func(ln[0],0,item,idx)}:func}var m_style,key,ln=m_this.line()(item,idx),style={};for(key in s_style)s_style.hasOwnProperty(key)&&(style[key]=wrapStyle(s_style[key]));m_style={data:[ln.map(function(d,i){return pos_func(d,i,item,idx)})],append:"path",attributes:{d:line},id:m_this._d3id()+idx,classes:["d3LineFeature","d3SubLine-"+idx],style:style},m_renderer._drawFeatures(m_style)}),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.lineFeature,geo.lineFeature),geo.registerFeature("d3","line",geo.d3.lineFeature),geo.d3.pathFeature=function(arg){"use strict";if(!(this instanceof geo.d3.pathFeature))return new geo.d3.pathFeature(arg);arg=arg||{},geo.pathFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,s_init=this._init,m_buildTime=geo.timestamp(),s_update=this._update,m_style={};return m_style.style={},this._init=function(arg){return s_init.call(m_this,arg),m_this},this._build=function(){var tmp,diag,data=m_this.data()||[],s_style=m_this.style(),m_renderer=m_this.renderer();return s_update.call(m_this),diag=function(d){var p={source:d.source,target:d.target};return d3.svg.diagonal()(p)},tmp=[],data.forEach(function(d,i){var src,trg;i=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.pathFeature,geo.pathFeature),geo.registerFeature("d3","path",geo.d3.pathFeature),geo.d3.graphFeature=function(arg){"use strict";var m_this=this;return this instanceof geo.d3.graphFeature?(geo.graphFeature.call(this,arg),this.select=function(){var renderer=m_this.renderer(),selection={},node=m_this.nodeFeature(),links=m_this.linkFeatures();return selection.nodes=renderer.select(node._d3id()),selection.links=links.map(function(link){return renderer.select(link._d3id())}),selection},this):new geo.d3.graphFeature(arg)},inherit(geo.d3.graphFeature,geo.graphFeature),geo.registerFeature("d3","graph",geo.d3.graphFeature),geo.d3.planeFeature=function(arg){"use strict";function normalize(pt){return Array.isArray(pt)?{x:pt[0],y:pt[1]}:pt}if(!(this instanceof geo.d3.planeFeature))return new geo.d3.planeFeature(arg);geo.planeFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,m_style={},s_update=this._update,s_init=this._init,m_buildTime=geo.timestamp();return this._build=function(){var origin=normalize(m_this.origin()),ul=normalize(m_this.upperLeft()),lr=normalize(m_this.lowerRight()),renderer=m_this.renderer(),s=m_this.style();return delete s.fill_color,delete s.color,delete s.opacity,s.screenCoordinates||(origin=renderer.worldToDisplay(origin),ul=renderer.worldToDisplay(ul),lr=renderer.worldToDisplay(lr)),m_style.id=m_this._d3id(),m_style.style=s,m_style.attributes={x:ul.x,y:ul.y,width:lr.x-origin.x,height:origin.y-ul.y},m_style.append="rect",m_style.data=[0],m_style.classes=["d3PlaneFeature"],renderer._drawFeatures(m_style),m_buildTime.modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.dataTime().getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init=function(arg){return s_init.call(m_this,arg||{}),m_this.style({stroke:function(){return!1},fill:function(){return!0},fillColor:function(){return{r:.3,g:.3,b:.3}},fillOpacity:function(){return.5}}),m_this},this._init(),this},inherit(geo.d3.planeFeature,geo.planeFeature),geo.registerFeature("d3","plane",geo.d3.planeFeature),geo.d3.vectorFeature=function(arg){"use strict";function markerID(d,i){return m_this._d3id()+"_marker_"+i}function updateMarkers(data,stroke,opacity){var renderer=m_this.renderer(),sel=m_this.renderer()._definitions().selectAll("marker.geo-vector").data(data);sel.enter().append("marker").attr("id",markerID).attr("class","geo-vector").attr("viewBox","0 0 10 10").attr("refX","1").attr("refY","5").attr("markerWidth","5").attr("markerHeight","5").attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z"),sel.exit().remove(),sel.style("stroke",renderer._convertColor(stroke)).style("fill",renderer._convertColor(stroke)).style("opacity",opacity)}if(!(this instanceof geo.d3.vectorFeature))return new geo.d3.vectorFeature(arg);arg=arg||{},geo.vectorFeature.call(this,arg),geo.d3.object.call(this);var m_sticky,m_this=this,s_init=this._init,s_exit=this._exit,s_update=this._update,m_buildTime=geo.timestamp(),m_style={};return this._init=function(arg){return s_init.call(m_this,arg),m_sticky=m_this.layer().sticky(),m_this},this._build=function(){function getScale(){return scale/m_renderer.scaleFactor()}var data=m_this.data(),s_style=m_this.style.get(),m_renderer=m_this.renderer(),orig_func=m_this.origin(),size_func=m_this.delta(),cache=[],scale=m_this.style("scale"),max=Number.NEGATIVE_INFINITY;return s_update.call(m_this),data||(data=[]),cache=data.map(function(d,i){var origin=m_renderer.worldToDisplay(orig_func(d,i)),delta=size_func(d,i);return max=Math.max(max,delta.x*delta.x+delta.y*delta.y),{x1:origin.x,y1:origin.y,dx:delta.x,dy:-delta.y}}),max=Math.sqrt(max),scale||(scale=75/max),m_style.id=m_this._d3id(),m_style.data=data,m_style.append="line",m_style.attributes={x1:function(d,i){return cache[i].x1},y1:function(d,i){return cache[i].y1},x2:function(d,i){return cache[i].x1+getScale()*cache[i].dx},y2:function(d,i){return cache[i].y1+getScale()*cache[i].dy},"marker-end":function(d,i){return"url(#"+markerID(d,i)+")"}},m_style.style={stroke:function(){return!0},strokeColor:s_style.strokeColor,strokeWidth:s_style.strokeWidth,strokeOpacity:s_style.strokeOpacity},m_style.classes=["d3VectorFeature"],updateMarkers(data,s_style.strokeColor,s_style.strokeOpacity),m_this.renderer()._drawFeatures(m_style),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()?m_this._build():updateMarkers(m_style.data,m_style.style.strokeColor,m_style.style.strokeOpacity),m_this},this._exit=function(){s_exit.call(m_this),m_style={},updateMarkers([],null,null)},this._init(arg),this},inherit(geo.d3.vectorFeature,geo.vectorFeature),geo.registerFeature("d3","vector",geo.d3.vectorFeature),geo.d3.d3Renderer=function(arg){"use strict";function setAttrs(select,attrs){var key;for(key in attrs)attrs.hasOwnProperty(key)&&select.attr(key,attrs[key])}function setStyles(select,styles){function fillFunc(){return styles.fill.apply(this,arguments)?null:"none"}function strokeFunc(){return styles.stroke.apply(this,arguments)?null:"none"}var key,k,f;for(key in styles)styles.hasOwnProperty(key)&&(f=null,k=null,"strokeColor"===key?(k="stroke",f=m_this._convertColor(styles[key],styles.stroke)):"stroke"===key&&styles[key]?(k="stroke",f=strokeFunc):"strokeWidth"===key?(k="stroke-width",f=m_this._convertScale(styles[key])):"strokeOpacity"===key?(k="stroke-opacity",f=styles[key]):"fillColor"===key?(k="fill",f=m_this._convertColor(styles[key],styles.fill)):"fill"!==key||styles.hasOwnProperty("fillColor")?"fillOpacity"===key&&(k="fill-opacity",f=styles[key]):(k="fill",f=fillFunc),k&&select.style(k,f))}function getMap(){var layer=m_this.layer();return layer?layer.map():null}function getGroup(){return m_svg.select(".group-"+m_this._d3id());
-}function initCorners(){var layer=m_this.layer(),map=layer.map(),width=m_this.layer().width(),height=m_this.layer().height();if(m_width=width,m_height=height,!m_width||!m_height)throw"Map layer has size 0";m_corners={upperLeft:map.displayToGcs({x:0,y:0}),lowerRight:map.displayToGcs({x:width,y:height})}}function setTransform(){if(m_corners||initCorners(),m_sticky){var dx,dy,scale,layer=m_this.layer(),map=layer.map(),upperLeft=map.gcsToDisplay(m_corners.upperLeft),lowerRight=map.gcsToDisplay(m_corners.lowerRight),group=getGroup();dx=upperLeft.x,dy=upperLeft.y,scale=(lowerRight.y-upperLeft.y)/m_height,group.attr("transform","matrix("+[scale,0,0,scale,dx,dy].join()+")"),m_scale=scale,m_dx=dx,m_dy=dy}}function baseToLocal(pt){return{x:(pt.x-m_dx)/m_scale,y:(pt.y-m_dy)/m_scale}}function localToBase(pt){return{x:pt.x*m_scale+m_dx,y:pt.y*m_scale+m_dy}}if(!(this instanceof geo.d3.d3Renderer))return new geo.d3.d3Renderer(arg);geo.renderer.call(this,arg);var s_exit=this._exit;geo.d3.object.call(this,arg),arg=arg||{};var m_this=this,m_sticky=null,m_features={},m_corners=null,m_width=null,m_height=null,m_scale=1,m_dx=0,m_dy=0,m_svg=null,m_defs=null;return this._convertColor=function(f,g){return f=geo.util.ensureFunction(f),g=g||function(){return!0},function(){var c="none";return g.apply(this,arguments)&&(c=f.apply(this,arguments),c.hasOwnProperty("r")&&c.hasOwnProperty("g")&&c.hasOwnProperty("b")&&(c=d3.rgb(255*c.r,255*c.g,255*c.b))),c}},this._convertPosition=function(f){return f=geo.util.ensureFunction(f),function(){return m_this.worldToDisplay(f.apply(this,arguments))}},this._convertScale=function(f){return f=geo.util.ensureFunction(f),function(){return f.apply(this,arguments)/m_scale}},this._init=function(){if(!m_this.canvas()){var canvas;m_svg=d3.select(m_this.layer().node().get(0)).append("svg"),m_defs=m_svg.append("defs");var shadow=m_defs.append("filter").attr("id","geo-highlight").attr("x","-100%").attr("y","-100%").attr("width","300%").attr("height","300%");shadow.append("feMorphology").attr("operator","dilate").attr("radius",2).attr("in","SourceAlpha").attr("result","dilateOut"),shadow.append("feGaussianBlur").attr("stdDeviation",5).attr("in","dilateOut").attr("result","blurOut"),shadow.append("feColorMatrix").attr("type","matrix").attr("values","-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0").attr("in","blurOut").attr("result","invertOut"),shadow.append("feBlend").attr("in","SourceGraphic").attr("in2","invertOut").attr("mode","normal"),canvas=m_svg.append("g"),shadow=m_defs.append("filter").attr("id","geo-blur").attr("x","-100%").attr("y","-100%").attr("width","300%").attr("height","300%"),shadow.append("feGaussianBlur").attr("stdDeviation",20).attr("in","SourceGraphic"),m_sticky=m_this.layer().sticky(),m_svg.attr("class",m_this._d3id()),m_svg.attr("width",m_this.layer().node().width()),m_svg.attr("height",m_this.layer().node().height()),canvas.attr("class","group-"+m_this._d3id()),m_this.canvas(canvas)}},this.displayToWorld=function(pt){var map=getMap();if(!map)throw"Cannot project until this layer is connected to a map.";return pt=Array.isArray(pt)?pt.map(function(x){return map.displayToGcs(localToBase(x))}):map.displayToGcs(localToBase(pt))},this.worldToDisplay=function(pt){var map=getMap();if(!map)throw"Cannot project until this layer is connected to a map.";var v;return v=Array.isArray(pt)?pt.map(function(x){return baseToLocal(map.gcsToDisplay(x))}):baseToLocal(map.gcsToDisplay(pt))},this.api=function(){return"d3"},this.scaleFactor=function(){return m_scale},this._resize=function(x,y,w,h){m_corners||initCorners(),m_svg.attr("width",w),m_svg.attr("height",h),setTransform(),m_this.layer().geoTrigger(geo.event.d3Rescale,{scale:m_scale},!0)},this._update=function(){},this._exit=function(){m_features={},m_this.canvas().remove(),s_exit()},this._definitions=function(){return m_defs},this._drawFeatures=function(arg){return m_features[arg.id]={data:arg.data,index:arg.dataIndex,style:arg.style,attributes:arg.attributes,classes:arg.classes,append:arg.append},m_this.__render(arg.id)},this.__render=function(id){var key;if(void 0===id){for(key in m_features)m_features.hasOwnProperty(key)&&m_this.__render(key);return m_this}var data=m_features[id].data,index=m_features[id].index,style=m_features[id].style,attributes=m_features[id].attributes,classes=m_features[id].classes,append=m_features[id].append,selection=m_this.select(id).data(data,index);return selection.enter().append(append),selection.exit().remove(),setAttrs(selection,attributes),selection.attr("class",classes.concat([id]).join(" ")),setStyles(selection,style),m_this},this.select=function(id){return getGroup().selectAll("."+id)},this._removeFeature=function(id){return m_this.select(id).remove(),delete m_features[id],m_this},this.draw=function(){},this.layer().geoOn(geo.event.pan,setTransform),this.layer().geoOn(geo.event.zoom,function(){setTransform(),m_this.__render(),m_this.layer().geoTrigger(geo.event.d3Rescale,{scale:m_scale},!0)}),this.layer().geoOn(geo.event.resize,function(event){m_this._resize(event.x,event.y,event.width,event.height)}),this._init(arg),this},inherit(geo.d3.d3Renderer,geo.renderer),geo.registerRenderer("d3",geo.d3.d3Renderer),geo.gui={},geo.gui.uiLayer=function(arg){"use strict";if(arg.renderer="d3",arg.sticky=!1,!(this instanceof geo.gui.uiLayer))return new geo.gui.uiLayer(arg);geo.layer.call(this,arg);var m_this=this,s_exit=this._exit;this.createWidget=function(widgetName,arg){var newWidget=geo.createWidget(widgetName,m_this,m_this.renderer(),arg);return m_this.addChild(newWidget),newWidget._init(),m_this.modified(),newWidget},this.deleteWidget=function(widget){return widget._exit(),m_this.removeChild(widget),m_this.modified(),m_this},this._exit=function(){m_this.children().forEach(function(child){m_this.deleteWidget(child)}),s_exit()}},inherit(geo.gui.uiLayer,geo.layer),geo.registerLayer("ui",geo.gui.uiLayer),geo.gui.widget=function(arg){"use strict";if(!(this instanceof geo.gui.widget))return new geo.gui.widget(arg);geo.sceneObject.call(this,arg);var m_this=this,s_exit=this._exit,m_layer=arg.layer;this._init=function(){m_this.modified()},this._exit=function(){m_this.children().forEach(function(child){m_this._deleteFeature(child)}),s_exit()},this._createFeature=function(featureName,arg){var newFeature=geo.createFeature(featureName,m_this,m_this.renderer(),arg);return m_this.addChild(newFeature),m_this.modified(),newFeature},this._deleteFeature=function(feature){return m_this.removeChild(feature),feature._exit(),m_this},this.layer=function(){return m_layer}},inherit(geo.gui.widget,geo.sceneObject),geo.gui.sliderWidget=function(arg){"use strict";function put_icon(icon,base,cx,cy,size){var g=base.append("g"),s=size/1024;return g.append("g").append("g").attr("transform","translate("+cx+","+cy+") scale("+s+") translate(-512,-512)").append("path").attr("d",icon).attr("class","geo-glyphicon"),g}if(!(this instanceof geo.gui.sliderWidget))return new geo.gui.sliderWidget(arg);geo.gui.widget.call(this,arg);var m_xscale,m_yscale,m_plus,m_minus,m_track,m_nub,m_plusIcon,m_minusIcon,m_group,m_lowContrast,m_this=this,s_exit=this._exit,m_width=20,m_height=100,m_nubSize=10,m_highlightDur=100;m_plusIcon="M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM564.326 564.326v206.182h-104.653v-206.182h-206.234v-104.653h206.182v-206.234h104.704v206.182h206.182v104.704h-206.182z",m_minusIcon="M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM770.56 459.674v104.704h-517.12v-104.704h517.12z",m_lowContrast={white:"#f4f4f4",black:"#505050"},this._init=function(){function respond(evt,trans){var z=m_yscale.invert(d3.mouse(m_this.layer().node()[0])[1]),zrange=map.zoomRange();z=(1-z)*(zrange.max-zrange.min)+zrange.min,trans?map.transition({zoom:z,ease:d3.ease("cubic-in-out"),duration:500,done:m_this._update()}):(map.zoom(z),m_this._update()),evt.stopPropagation()}var svg=m_this.layer().renderer().canvas(),x0=40,y0=40+m_width,map=m_this.layer().map();m_xscale=d3.scale.linear().domain([-4,4]).range([x0,x0+m_width]),m_yscale=d3.scale.linear().domain([0,1]).range([y0,y0+m_height]),svg=svg.append("g").classed("geo-ui-slider",!0),m_group=svg,m_plus=svg.append("g"),m_plus.append("circle").datum({fill:"white",stroke:null}).classed("geo-zoom-in",!0).attr("cx",m_xscale(0)).attr("cy",m_yscale(0)-m_width+2).attr("r",m_width/2).style({cursor:"pointer"}).on("click",function(){var z=map.zoom();map.transition({zoom:z+1,ease:d3.ease("cubic-in-out"),duration:500})}).on("mousedown",function(){d3.event.stopPropagation()}),put_icon(m_plusIcon,m_plus,m_xscale(0),m_yscale(0)-m_width+2,m_width+5).style("cursor","pointer").style("pointer-events","none").select("path").datum({fill:"black",stroke:null}),m_minus=svg.append("g"),m_minus.append("circle").datum({fill:"white",stroke:null}).classed("geo-zoom-out",!0).attr("cx",m_xscale(0)).attr("cy",m_yscale(1)+m_width-2).attr("r",m_width/2).style({cursor:"pointer"}).on("click",function(){var z=map.zoom();map.transition({zoom:z-1,ease:d3.ease("cubic-in-out"),duration:500})}).on("mousedown",function(){d3.event.stopPropagation()}),put_icon(m_minusIcon,m_minus,m_xscale(0),m_yscale(1)+m_width-2,m_width+5).style("cursor","pointer").style("pointer-events","none").select("path").datum({fill:"black",stroke:null}),m_track=svg.append("rect").datum({fill:"white",stroke:"black"}).classed("geo-zoom-track",!0).attr("x",m_xscale(0)-m_width/6).attr("y",m_yscale(0)).attr("rx",m_width/10).attr("ry",m_width/10).attr("width",m_width/3).attr("height",m_height).style({cursor:"pointer"}).on("click",function(){respond(d3.event,!0)}),m_nub=svg.append("rect").datum({fill:"black",stroke:null}).classed("geo-zoom-nub",!0).attr("x",m_xscale(-4)).attr("y",m_yscale(.5)-m_nubSize/2).attr("rx",3).attr("ry",3).attr("width",m_width).attr("height",m_nubSize).style({cursor:"pointer"}).on("mousedown",function(){d3.select(document).on("mousemove.geo.slider",function(){respond(d3.event)}),d3.select(document).on("mouseup.geo.slider",function(){respond(d3.event),d3.select(document).on(".geo.slider",null)}),d3.event.stopPropagation()});var mouseOver=function(){d3.select(this).attr("filter","url(#geo-highlight)"),m_group.selectAll("rect,path,circle").transition().duration(m_highlightDur).style("fill",function(d){return d.fill||null}).style("stroke",function(d){return d.stroke||null})},mouseOut=function(){d3.select(this).attr("filter",null),m_group.selectAll("circle,rect,path").transition().duration(m_highlightDur).style("fill",function(d){return m_lowContrast[d.fill]||null}).style("stroke",function(d){return m_lowContrast[d.stroke]||null})};m_group.selectAll("*").on("mouseover",mouseOver).on("mouseout",mouseOut),m_this.layer().geoOn(geo.event.zoom,function(){m_this._update()}),mouseOut(),m_this._update()},this._exit=function(){m_group.remove(),m_this.layer().geoOff(geo.event.zoom),s_exit()},this._update=function(obj){var map=m_this.layer().map(),zoomRange=map.zoomRange(),zoom=map.zoom(),zoomScale=d3.scale.linear();obj=obj||{},zoom=obj.value||zoom,zoomScale.domain([zoomRange.min,zoomRange.max]).range([1,0]).clamp(!0),m_nub.attr("y",m_yscale(zoomScale(zoom))-m_nubSize/2)}},inherit(geo.gui.sliderWidget,geo.gui.widget),geo.registerWidget("d3","slider",geo.gui.sliderWidget),geo.gui.legendWidget=function(arg){"use strict";if(!(this instanceof geo.gui.legendWidget))return new geo.gui.legendWidget(arg);geo.gui.widget.call(this,arg);var m_this=this,m_categories=[],m_top=null,m_group=null,m_border=null,m_spacing=20,m_padding=12;this.categories=function(arg){return void 0===arg?m_categories.slice():(m_categories=arg.slice().map(function(d){return"line"===d.type&&(d.style.fill=!1,d.style.stroke=!0),d}),m_this.draw(),m_this)},this.size=function(){var height,width=1,test=m_this.layer().renderer().canvas().append("text").style("opacity",1e-6);return m_categories.forEach(function(d){test.text(d.name),width=Math.max(width,test.node().getBBox().width)}),test.remove(),height=m_spacing*(m_categories.length+1),{width:width+50,height:height}},this.draw=function(){function applyColor(selection){selection.style("fill",function(d){return d.style.fill||void 0===d.style.fill?d.style.fillColor:"none"}).style("fill-opacity",function(d){return void 0===d.style.fillOpacity?1:d.style.fillOpacity}).style("stroke",function(d){return d.style.stroke||void 0===d.style.stroke?d.style.strokeColor:"none"}).style("stroke-opacity",function(d){return void 0===d.style.strokeOpacity?1:d.style.strokeOpacity}).style("stroke-width",function(d){return void 0===d.style.strokeWidth?1.5:d.style.strokeWidth})}m_this._init(),m_border.attr("height",m_this.size().height+2*m_padding).style("display",null);var scale=m_this._scale(),labels=m_group.selectAll("g.geo-label").data(m_categories,function(d){return d.name}),g=labels.enter().append("g").attr("class","geo-label").attr("transform",function(d,i){return"translate(0,"+scale.y(i)+")"});return applyColor(g.filter(function(d){return"point"!==d.type&&"line"!==d.type}).append("rect").attr("x",0).attr("y",-6).attr("rx",5).attr("ry",5).attr("width",40).attr("height",12)),applyColor(g.filter(function(d){return"point"===d.type}).append("circle").attr("cx",20).attr("cy",0).attr("r",6)),applyColor(g.filter(function(d){return"line"===d.type}).append("line").attr("x1",0).attr("y1",0).attr("x2",40).attr("y2",0)),g.append("text").attr("x","50px").attr("y",0).attr("dy","0.3em").text(function(d){return d.name}),m_this},this._scale=function(){return{x:d3.scale.linear().domain([0,1]).range([0,m_this.size().width]),y:d3.scale.linear().domain([0,m_categories.length-1]).range([m_padding/2,m_this.size().height-m_padding/2])}},this._init=function(){var w=m_this.size().width+2*m_padding,h=m_this.size().height+2*m_padding,nw=m_this.layer().map().node().width(),margin=20;m_top&&m_top.remove(),m_top=m_this.layer().renderer().canvas().append("g").attr("transform","translate("+(nw-w-margin)+","+margin+")"),m_group=m_top.append("g").attr("transform","translate("+[m_padding-1.5,m_padding]+")"),m_border=m_group.append("rect").attr("x",-m_padding).attr("y",-m_padding).attr("width",w).attr("height",h).attr("rx",3).attr("ry",3).style({stroke:"black","stroke-width":"1.5px",fill:"white","fill-opacity":.75,display:"none"}),m_group.on("mousedown",function(){d3.event.stopPropagation()}),m_group.on("mouseover",function(){m_border.transition().duration(250).style("fill-opacity",1)}),m_group.on("mouseout",function(){m_border.transition().duration(250).style("fill-opacity",.75)})},this.geoOn(geo.event.resize,function(){this.draw()})},inherit(geo.gui.legendWidget,geo.gui.widget),geo.registerWidget("d3","legend",geo.gui.legendWidget),function($,geo,d3){"use strict";var load=function(){function isColorKey(key){return"color"===key.slice(key.length-5,key.length).toLowerCase()}function makeColorScale(data,acc){function wrap(func){return geo.util.isFunction(func)?function(){return func(acc.apply(this,arguments))}:func(acc)}if(!d3)return console.warn("d3 is unavailable, cannot apply color scales."),acc;var domain,cannotHandle=!1,doNotHandle=!0,categorical=!1,min=Number.POSITIVE_INFINITY,max=Number.NEGATIVE_INFINITY;return domain=geo.util.isFunction(acc)?d3.set(data.map(acc)).values():[acc],domain.forEach(function(v){("string"!=typeof v||"object"!=typeof geo.util.convertColor(v))&&(doNotHandle=!1),"string"==typeof v?categorical=!0:isFinite(v)?+v>max?max=+v:min>+v&&(min=+v):cannotHandle=!0}),cannotHandle?acc:doNotHandle?acc:wrap(categorical?domain.length<=10?d3.scale.category10().domain(domain):domain.length<=20?d3.scale.category20().domain(domain):d3.scale.category20().domain(domain):d3.scale.linear().range(["rgb(252,141,89)","rgb(255,255,191)","rgb(145,191,219)"]).domain([min,(min+max)/2,max]))}return $.widget?void $.widget("geojs.geojsMap",{options:{center:{latitude:0,longitude:0},zoom:0,width:null,height:null,layers:[],data:[],tileUrl:"http://tile.openstreetmap.org///.png",attribution:void 0,baseLayer:"osm",baseRenderer:"vgl"},_create:function(){!this._map&&this.element.length&&(this._map=geo.map({width:this.options.width,height:this.options.height,zoom:this.options.zoom,center:this.options.center,node:this.element.get(0)}),this._baseLayer=this._map.createLayer(this.options.baseLayer,{renderer:this.options.baseRenderer,tileUrl:this.options.tileUrl,attribution:this.options.attribution}),this._resize({width:800,height:600}),this._layers=[],this.update())},update:function(layers){var m_this=this;return this.options.layers=layers||this.options.layers||[],this._layers.forEach(function(layer){layer.clear(),m_this._map.deleteLayer(layer)}),this._layers=this.options.layers.map(function(layer){return layer.data=layer.data||m_this.options.data,(layer.features||[]).forEach(function(feature){var scl,data=feature.data||layer.data||[];"point"===feature.type&&(feature.size?feature._size=geo.util.ensureFunction(feature.size):null===feature.size&&delete feature._size,data.length&&feature._size&&(scl=d3.scale.linear().domain(d3.extent(data,feature._size)).range([5,20]),feature.radius=function(){return scl(feature._size.apply(this,arguments))}),delete feature.size);var key;for(key in feature)feature.hasOwnProperty(key)&&isColorKey(key)&&(feature[key]=makeColorScale(data,feature[key]))}),geo.layer.create(m_this._map,layer)}),this.redraw(),this},map:function(){return this._map},tileUrl:function(url){return this._baseLayer.tileUrl(url),this._baseLayer.updateBaseUrl(),this},_resize:function(size){var width=this.options.width,height=this.options.height;size&&(width=size.width,height=size.height),width||(width=this.element.width()),height||(height=this.element.height()),this._map.resize(0,0,width,height)},redraw:function(){return this._resize(),this}}):void($.fn.geojsMap=function(){throw new Error("The geojs jquery plugin requires jquery ui to be available.")})};$(load)}($||window.$,geo||window.geo,d3||window.d3);
\ No newline at end of file
+function inherit(C,P){"use strict";var F=function(){};F.prototype=P.prototype,C.prototype=new F,C.uber=P.prototype,C.prototype.constructor=C}var geo={};if(window.geo=geo,geo.renderers={},geo.features={},geo.fileReaders={},geo.rendererLayerAdjustments={},geo.inherit=function(C,P){"use strict";var F=inherit.func();F.prototype=P.prototype,C.prototype=new F,C.prototype.constructor=C},geo.inherit.func=function(){"use strict";return function(){}},window.inherit=geo.inherit,geo.extend=function(props){"use strict";var child=Object.create(this.prototype);return $.extend(child.prototype,props||{}),child},geo.registerFileReader=function(name,func){"use strict";void 0===geo.fileReaders&&(geo.fileReaders={}),geo.fileReaders[name]=func},geo.createFileReader=function(name,opts){"use strict";return geo.fileReaders.hasOwnProperty(name)?geo.fileReaders[name](opts):null},geo.registerRenderer=function(name,func){"use strict";void 0===geo.renderers&&(geo.renderers={}),geo.renderers[name]=func},geo.createRenderer=function(name,layer,canvas,options){"use strict";if(geo.renderers.hasOwnProperty(name)){var ren=geo.renderers[name]({layer:layer,canvas:canvas,options:options});return ren._init(),ren}return null},geo.registerFeature=function(category,name,func){"use strict";void 0===geo.features&&(geo.features={}),category in geo.features||(geo.features[category]={}),geo.features[category][name]=func},geo.createFeature=function(name,layer,renderer,arg){"use strict";var category=renderer.api(),options={layer:layer,renderer:renderer};if(category in geo.features&&name in geo.features[category]){void 0!==arg&&$.extend(!0,options,arg);var feature=geo.features[category][name](options);return layer.gcs=function(){return layer.map().gcs()},feature}return null},geo.registerLayerAdjustment=function(category,name,func){"use strict";void 0===geo.rendererLayerAdjustments&&(geo.rendererLayerAdjustments={}),category in geo.rendererLayerAdjustments||(geo.rendererLayerAdjustments[category]={}),geo.rendererLayerAdjustments[category][name]=func},geo.adjustLayerForRenderer=function(name,layer){"use strict";var rendererName=layer.rendererName();rendererName&&geo.rendererLayerAdjustments&&geo.rendererLayerAdjustments[rendererName]&&geo.rendererLayerAdjustments[rendererName][name]&&geo.rendererLayerAdjustments[rendererName][name].apply(layer)},geo.registerLayer=function(name,func){"use strict";void 0===geo.layers&&(geo.layers={}),geo.layers[name]=func},geo.createLayer=function(name,map,arg){"use strict";var options={map:map,renderer:"vgl"},layer=null;return name in geo.layers?(void 0!==arg&&$.extend(!0,options,arg),layer=geo.layers[name](options),layer._init(),layer):null},geo.registerWidget=function(category,name,func){"use strict";void 0===geo.widgets&&(geo.widgets={}),category in geo.widgets||(geo.widgets[category]={}),geo.widgets[category][name]=func},geo.createWidget=function(name,layer,arg){"use strict";var options={layer:layer};if(name in geo.widgets.dom)return void 0!==arg&&$.extend(!0,options,arg),geo.widgets.dom[name](options);throw new Error("Cannot create unknown widget "+name)},window.requestAnimationFrame||(window.requestAnimationFrame=function(func){"use strict";window.setTimeout(func,15)}),Math.log2||(Math.log2=function(){"use strict";return Math.log.apply(Math,arguments)/Math.LN2}),Math.sinh=Math.sinh||function(x){"use strict";var y=Math.exp(x);return(y-1/y)/2},geo.version="0.6.0-rc.1","undefined"==typeof ogs)var ogs={};ogs.namespace=function(ns_string){"use strict";var i,parts=ns_string.split("."),parent=ogs;for("ogs"===parts[0]&&(parts=parts.slice(1)),i=0;ithis.computeBoundsTimestamp().getMTime()&&this.m_parent.boundsDirtyTimestamp.modified(),this.computeBounds(),visitor.mode()===visitor.TraverseAllChildren)for(i=0;ithis.boundsDirtyTimestamp().getMTime()))for(i=0;ii;i+=1)istep=2*i,jstep=2*i+1,childBounds[istep]bounds[jstep]&&(bounds[jstep]=childBounds[jstep]);this.setBounds(bounds[0],bounds[1],bounds[2],bounds[3],bounds[4],bounds[5])}},this},inherit(vgl.groupNode,vgl.node),vgl.actor=function(){"use strict";if(!(this instanceof vgl.actor))return new vgl.actor;vgl.node.call(this);var m_this=this,m_transformMatrix=mat4.create(),m_referenceFrame=vgl.boundingObject.ReferenceFrame.Relative,m_mapper=null;return this.matrix=function(){return m_transformMatrix},this.setMatrix=function(tmatrix){tmatrix!==m_transformMatrix&&(m_transformMatrix=tmatrix,m_this.modified())},this.referenceFrame=function(){return m_referenceFrame},this.setReferenceFrame=function(referenceFrame){return referenceFrame!==m_referenceFrame?(m_referenceFrame=referenceFrame,m_this.modified(),!0):!1},this.mapper=function(){return m_mapper},this.setMapper=function(mapper){mapper!==m_mapper&&(m_mapper=mapper,m_this.boundsModified())},this.accept=function(visitor){visitor=visitor},this.ascend=function(visitor){visitor=visitor},this.computeLocalToWorldMatrix=function(matrix,visitor){matrix=matrix,visitor=visitor},this.computeWorldToLocalMatrix=function(matrix,visitor){matrix=matrix,visitor=visitor},this.computeBounds=function(){if(null===m_mapper||void 0===m_mapper)return void m_this.resetBounds();var mapperBounds,minPt,maxPt,newBounds,computeBoundsTimestamp=m_this.computeBoundsTimestamp();(m_this.boundsDirtyTimestamp().getMTime()>computeBoundsTimestamp.getMTime()||m_mapper.boundsDirtyTimestamp().getMTime()>computeBoundsTimestamp.getMTime())&&(m_mapper.computeBounds(),mapperBounds=m_mapper.bounds(),minPt=[mapperBounds[0],mapperBounds[2],mapperBounds[4]],maxPt=[mapperBounds[1],mapperBounds[3],mapperBounds[5]],vec3.transformMat4(minPt,minPt,m_transformMatrix),vec3.transformMat4(maxPt,maxPt,m_transformMatrix),newBounds=[minPt[0]>maxPt[0]?maxPt[0]:minPt[0],minPt[0]>maxPt[0]?minPt[0]:maxPt[0],minPt[1]>maxPt[1]?maxPt[1]:minPt[1],minPt[1]>maxPt[1]?minPt[1]:maxPt[1],minPt[2]>maxPt[2]?maxPt[2]:minPt[2],minPt[2]>maxPt[2]?minPt[2]:maxPt[2]],m_this.setBounds(newBounds[0],newBounds[1],newBounds[2],newBounds[3],newBounds[4],newBounds[5]),computeBoundsTimestamp.modified())},m_this},inherit(vgl.actor,vgl.node),vgl.freezeObject=function(obj){"use strict";var freezedObject=Object.freeze(obj);return"undefined"==typeof freezedObject&&(freezedObject=function(o){return o}),freezedObject},vgl.defaultValue=function(a,b){"use strict";return"undefined"!=typeof a?a:b},vgl.defaultValue.EMPTY_OBJECT=vgl.freezeObject({}),vgl.graphicsObject=function(type){"use strict";if(type=type,!(this instanceof vgl.graphicsObject))return new vgl.graphicsObject;vgl.object.call(this);var m_this=this;return this._setup=function(renderState){return renderState=renderState,!1},this._cleanup=function(renderState){return renderState=renderState,!1},this.bind=function(renderState){return renderState=renderState,!1},this.undoBind=function(renderState){return renderState=renderState,!1},this.render=function(renderState){return renderState=renderState,!1},this.remove=function(renderState){m_this._cleanup(renderState)},m_this},inherit(vgl.graphicsObject,vgl.object),vgl.geojsonReader=function(){"use strict";return this instanceof vgl.geojsonReader?(this.readScalars=function(coordinates,geom,size_estimate,idx){var array=null,s=null,r=null,g=null,b=null;"values"===this.m_scalarFormat&&4===coordinates.length?(s=coordinates[3],array=geom.sourceData(vgl.vertexAttributeKeys.Scalar),array||(array=new vgl.sourceDataSf,this.m_scalarRange&&array.setScalarRange(this.m_scalarRange[0],this.m_scalarRange[1]),void 0!==size_estimate&&(array.data().length=size_estimate),geom.addSource(array)),void 0===size_estimate?array.pushBack(s):array.insertAt(idx,s)):"rgb"===this.m_scalarFormat&&6===coordinates.length&&(array=geom.sourceData(vgl.vertexAttributeKeys.Color),array||(array=new vgl.sourceDataC3fv,void 0!==size_estimate&&(array.length=3*size_estimate),geom.addSource(array)),r=coordinates[3],g=coordinates[4],b=coordinates[5],void 0===size_estimate?array.pushBack([r,g,b]):array.insertAt(idx,[r,g,b]))},this.readPoint=function(coordinates){var geom=new vgl.geometryData,vglpoints=new vgl.points,vglcoords=new vgl.sourceDataP3fv,indices=new Uint16Array(1),x=null,y=null,z=null,i=null;for(geom.addSource(vglcoords),i=0;1>i;i+=1)indices[i]=i,x=coordinates[0],y=coordinates[1],z=0,coordinates.length>2&&(z=coordinates[2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates,geom);return vglpoints.setIndices(indices),geom.addPrimitive(vglpoints),geom.setName("aPoint"),geom},this.readMultiPoint=function(coordinates){var i,geom=new vgl.geometryData,vglpoints=new vgl.points,vglcoords=new vgl.sourceDataP3fv,indices=new Uint16Array(coordinates.length),pntcnt=0,estpntcnt=coordinates.length,x=null,y=null,z=null;for(vglcoords.data().length=3*estpntcnt,i=0;i2&&(z=coordinates[i][2]),vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[i],geom,estpntcnt,pntcnt),pntcnt+=1;return vglpoints.setIndices(indices),geom.addPrimitive(vglpoints),geom.addSource(vglcoords),geom.setName("manyPoints"),geom},this.readLineString=function(coordinates){var geom=new vgl.geometryData,vglline=new vgl.lineStrip,vglcoords=new vgl.sourceDataP3fv,indices=[],i=null,x=null,y=null,z=null;for(vglline.setIndicesPerPrimitive(coordinates.length),i=0;i2&&(z=coordinates[i][2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates[i],geom);return vglline.setIndices(indices),geom.addPrimitive(vglline),geom.addSource(vglcoords),geom.setName("aLineString"),geom},this.readMultiLineString=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,pntcnt=0,estpntcnt=2*coordinates.length,i=null,j=null,x=null,y=null,z=null,indices=null,vglline=null,thisLineLength=null;for(vglcoords.data().length=3*estpntcnt,j=0;ji;i+=1)indices.push(pntcnt),x=coordinates[j][i][0],y=coordinates[j][i][1],z=0,coordinates[j][i].length>2&&(z=coordinates[j][i][2]),vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[j][i],geom,2*estpntcnt,pntcnt),pntcnt+=1;vglline.setIndices(indices),geom.addPrimitive(vglline)}return geom.setName("aMultiLineString"),geom.addSource(vglcoords),geom},this.readPolygon=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,x=null,y=null,z=null,thisPolyLength=coordinates[0].length,vl=1,i=null,indices=null,vgltriangle=null;for(i=0;thisPolyLength>i;i+=1)x=coordinates[0][i][0],y=coordinates[0][i][1],z=0,coordinates[0][i].length>2&&(z=coordinates[0][i][2]),vglcoords.pushBack([x,y,z]),this.readScalars(coordinates[0][i],geom),i>1&&(indices=new Uint16Array([0,vl,i]),vgltriangle=new vgl.triangles,vgltriangle.setIndices(indices),geom.addPrimitive(vgltriangle),vl=i);return geom.setName("POLY"),geom.addSource(vglcoords),geom},this.readMultiPolygon=function(coordinates){var geom=new vgl.geometryData,vglcoords=new vgl.sourceDataP3fv,ccount=0,numPolys=coordinates.length,pntcnt=0,estpntcnt=3*numPolys,vgltriangle=new vgl.triangles,indexes=[],i=null,j=null,x=null,y=null,z=null,thisPolyLength=null,vf=null,vl=null,flip=null,flipped=!1,tcount=0;for(vglcoords.data().length=3*numPolys,j=0;numPolys>j;j+=1)for(thisPolyLength=coordinates[j][0].length,vf=ccount,vl=ccount+1,flip=[!1,!1,!1],i=0;thisPolyLength>i;i+=1)x=coordinates[j][0][i][0],y=coordinates[j][0][i][1],z=0,coordinates[j][0][i].length>2&&(z=coordinates[j][0][i][2]),flipped=!1,x>180&&(flipped=!0,x-=360),0===i?flip[0]=flipped:flip[1+(i-1)%2]=flipped,vglcoords.insertAt(pntcnt,[x,y,z]),this.readScalars(coordinates[j][0][i],geom,estpntcnt,pntcnt),pntcnt+=1,i>1&&(flip[0]===flip[1]&&flip[1]===flip[2]&&(indexes[3*tcount+0]=vf,indexes[3*tcount+1]=vl,indexes[3*tcount+2]=ccount,tcount+=1),vl=ccount),ccount+=1;return vgltriangle.setIndices(indexes),geom.addPrimitive(vgltriangle),geom.setName("aMultiPoly"),geom.addSource(vglcoords),geom},this.readGJObjectInt=function(object){if(!object.hasOwnProperty("type"))return null;object.properties&&object.properties.ScalarFormat&&"values"===object.properties.ScalarFormat&&(this.m_scalarFormat="values",object.properties.ScalarRange&&(this.m_scalarRange=object.properties.ScalarRange)),object.properties&&object.properties.ScalarFormat&&"rgb"===object.properties.ScalarFormat&&(this.m_scalarFormat="rgb");var ret,type=object.type,next=null,nextset=null,i=null;switch(type){case"Point":ret=this.readPoint(object.coordinates);break;case"MultiPoint":ret=this.readMultiPoint(object.coordinates);break;case"LineString":ret=this.readLineString(object.coordinates);break;case"MultiLineString":ret=this.readMultiLineString(object.coordinates);break;case"Polygon":ret=this.readPolygon(object.coordinates);break;case"MultiPolygon":ret=this.readMultiPolygon(object.coordinates);break;case"GeometryCollection":for(nextset=[],i=0;im_max)&&(m_max=value),(null===m_min||m_min>value)&&(m_min=value),this.data()[this.data().length]=value},this.insertAt=function(index,value){(null===m_max||value>m_max)&&(m_max=value),(null===m_min||m_min>value)&&(m_min=value),this.data()[index]=value},this.scalarRange=function(){return null===m_fixedmin||null===m_fixedmax?[m_min,m_max]:[m_fixedmin,m_fixedmax]},this.setScalarRange=function(min,max){m_fixedmin=min,m_fixedmax=max},this},inherit(vgl.sourceDataSf,vgl.sourceData),vgl.sourceDataDf=function(arg){"use strict";return this instanceof vgl.sourceDataDf?(vgl.sourceData.call(this,arg),this.addAttribute(vgl.vertexAttributeKeys.Scalar,vgl.GL.FLOAT,4,0,4,1,!1),this.pushBack=function(value){this.data()[this.data().length]=value},this.insertAt=function(index,value){this.data()[index]=value},this):new vgl.sourceDataDf(arg)},inherit(vgl.sourceDataDf,vgl.sourceData),vgl.geometryData=function(){"use strict";if(!(this instanceof vgl.geometryData))return new vgl.geometryData;vgl.data.call(this);var m_name="",m_primitives=[],m_sources=[],m_bounds=[0,0,0,0,0,0],m_computeBoundsTimestamp=vgl.timestamp(),m_boundsDirtyTimestamp=vgl.timestamp();return this.type=function(){return vgl.data.geometry},this.name=function(){return m_name},this.setName=function(name){m_name=name},this.addSource=function(source,sourceName){return void 0!==sourceName&&source.setName(sourceName),-1===m_sources.indexOf(source)?(m_sources.push(source),source.hasKey(vgl.vertexAttributeKeys.Position)&&m_boundsDirtyTimestamp.modified(),!0):!1},this.source=function(index){return indexm_computeBoundsTimestamp.getMTime()&&this.computeBounds(),m_bounds},this.boundsDirty=function(dirty){return dirty&&m_boundsDirtyTimestamp.modified(),m_boundsDirtyTimestamp.getMTime()>m_computeBoundsTimestamp.getMTime()},this.resetBounds=function(){m_bounds[0]=0,m_bounds[1]=0,m_bounds[2]=0,m_bounds[3]=0,m_bounds[4]=0,m_bounds[5]=0},this.setBounds=function(minX,maxX,minY,maxY,minZ,maxZ){return m_bounds[0]=minX,m_bounds[1]=maxX,m_bounds[2]=minY,m_bounds[3]=maxY,m_bounds[4]=minZ,m_bounds[5]=maxZ,m_computeBoundsTimestamp.modified(),!0},this.computeBounds=function(){if(m_boundsDirtyTimestamp.getMTime()>m_computeBoundsTimestamp.getMTime()){var j,ib,jb,maxv,minv,vertexIndex,attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),data=sourceData.data(),numberOfComponents=sourceData.attributeNumberOfComponents(attr),stride=sourceData.attributeStride(attr),offset=sourceData.attributeOffset(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),count=data.length,value=null;for(stride/=sizeOfDataType,offset/=sizeOfDataType,this.resetBounds(),j=0;numberOfComponents>j;j+=1){for(ib=2*j,jb=2*j+1,maxv=minv=count?m_bounds[jb]=data[offset+j]:0,vertexIndex=offset+stride+j;count>vertexIndex;vertexIndex+=stride)value=data[vertexIndex],value>maxv&&(maxv=value),minv>value&&(minv=value);m_bounds[ib]=minv,m_bounds[jb]=maxv}m_computeBoundsTimestamp.modified()}},this.findClosestVertex=function(point){var vi,vPos,dx,dy,dz,dist,i,attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),numberOfComponents=sourceData.attributeNumberOfComponents(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType,minDist=Number.MAX_VALUE,minIndex=null;for(3!==numberOfComponents&&console.log("[warning] Find closest vertex assumes threecomponent vertex "),point.z||(point={x:point.x,y:point.y,z:0}),vi=offset,i=0;vidist&&(minDist=dist,minIndex=i);return minIndex},this.getPosition=function(index){var attr=vgl.vertexAttributeKeys.Position,sourceData=this.sourceData(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),numberOfComponents=sourceData.attributeNumberOfComponents(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType;return 3!==numberOfComponents&&console.log("[warning] getPosition assumes three component data"),[data[offset+index*stride],data[offset+index*stride+1],data[offset+index*stride+2]]},this.getScalar=function(index){var numberOfComponents,sizeOfDataType,data,stride,offset,attr=vgl.vertexAttributeKeys.Scalar,sourceData=this.sourceData(attr);return sourceData?(numberOfComponents=sourceData.attributeNumberOfComponents(attr),sizeOfDataType=sourceData.sizeOfAttributeDataType(attr),data=sourceData.data(),stride=sourceData.attributeStride(attr)/sizeOfDataType,offset=sourceData.attributeOffset(attr)/sizeOfDataType,index*stride+offset>=data.length&&console.log("access out of bounds in getScalar"),data[index*stride+offset]):null},this},inherit(vgl.geometryData,vgl.data),vgl.mapper=function(arg){"use strict";function deleteVertexBufferObjects(renderState){var i;for(i=0;ii;i+=1){for(bufferId=m_context.createBuffer(),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,bufferId),data=m_geomData.source(i).data(),data instanceof Float32Array||(data=new Float32Array(data)),m_context.bufferData(vgl.GL.ARRAY_BUFFER,data,m_dynamicDraw?vgl.GL.DYNAMIC_DRAW:vgl.GL.STATIC_DRAW),keys=m_geomData.source(i).keys(),ks=[],j=0;jk;k+=1)bufferId=m_context.createBuffer(),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,bufferId),m_context.bufferData(vgl.GL.ARRAY_BUFFER,m_geomData.primitive(k).indices(),vgl.GL.STATIC_DRAW),m_buffers[i]=bufferId,i+=1;m_glCompileTimestamp.modified()}}function cleanUpDrawObjects(renderState){renderState=renderState,m_bufferVertexAttributeMap={},m_buffers=[]}function setupDrawObjects(renderState){deleteVertexBufferObjects(renderState),cleanUpDrawObjects(renderState),createVertexBufferObjects(renderState),m_dirty=!1}if(!(this instanceof vgl.mapper))return new vgl.mapper(arg);vgl.boundingObject.call(this),arg=arg||{};var m_dirty=!0,m_color=[0,1,1],m_geomData=null,m_buffers=[],m_bufferVertexAttributeMap={},m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,m_glCompileTimestamp=vgl.timestamp(),m_context=null;return this.computeBounds=function(){if(null===m_geomData||"undefined"==typeof m_geomData)return void this.resetBounds();var computeBoundsTimestamp=this.computeBoundsTimestamp(),boundsDirtyTimestamp=this.boundsDirtyTimestamp(),geomBounds=null;boundsDirtyTimestamp.getMTime()>computeBoundsTimestamp.getMTime()&&(geomBounds=m_geomData.bounds(),this.setBounds(geomBounds[0],geomBounds[1],geomBounds[2],geomBounds[3],geomBounds[4],geomBounds[5]),computeBoundsTimestamp.modified())},this.color=function(){return m_color},this.setColor=function(r,g,b){m_color[0]=r,m_color[1]=g,m_color[2]=b,this.modified()},this.geometryData=function(){return m_geomData},this.setGeometryData=function(geom){m_geomData!==geom&&(m_geomData=geom,this.modified(),this.boundsDirtyTimestamp().modified())},this.updateSourceBuffer=function(sourceName,values,renderState){if(renderState&&(m_context=renderState.m_context),!m_context)return!1;for(var bufferIndex=-1,i=0;ibufferIndex||bufferIndex>=m_buffers.length?!1:(values||(values=m_geomData.source(i).dataToFloat32Array()),m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),values instanceof Float32Array?m_context.bufferSubData(vgl.GL.ARRAY_BUFFER,0,values):m_context.bufferSubData(vgl.GL.ARRAY_BUFFER,0,new Float32Array(values)),!0)},this.getSourceBuffer=function(sourceName){var source=m_geomData.sourceByName(sourceName);return source?source.dataToFloat32Array():new Float32Array},this.render=function(renderState){(this.getMTime()>m_glCompileTimestamp.getMTime()||renderState.m_contextChanged)&&setupDrawObjects(renderState),m_context=renderState.m_context,m_context.vertexAttrib3fv(vgl.vertexAttributeKeys.Color,this.color());var i,bufferIndex=0,j=0,noOfPrimitives=null,primitive=null;for(i in m_bufferVertexAttributeMap)if(m_bufferVertexAttributeMap.hasOwnProperty(i)){for(m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),j=0;jj;j+=1){switch(m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,m_buffers[bufferIndex]),bufferIndex+=1,primitive=m_geomData.primitive(j),primitive.primitiveType()){case vgl.GL.POINTS:m_context.drawArrays(vgl.GL.POINTS,0,primitive.numberOfIndices());break;case vgl.GL.LINES:m_context.drawArrays(vgl.GL.LINES,0,primitive.numberOfIndices());break;case vgl.GL.LINE_STRIP:m_context.drawArrays(vgl.GL.LINE_STRIP,0,primitive.numberOfIndices());break;case vgl.GL.TRIANGLES:m_context.drawArrays(vgl.GL.TRIANGLES,0,primitive.numberOfIndices());break;case vgl.GL.TRIANGLE_STRIP:m_context.drawArrays(vgl.GL.TRIANGLE_STRIP,0,primitive.numberOfIndices())}m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,null)}},this},inherit(vgl.mapper,vgl.boundingObject),vgl.groupMapper=function(){"use strict";if(!(this instanceof vgl.groupMapper))return new vgl.groupMapper;vgl.mapper.call(this);var m_createMappersTimestamp=vgl.timestamp(),m_mappers=[],m_geomDataArray=[];return this.geometryData=function(index){return void 0!==index&&index0?m_geomDataArray[0]:null},this.setGeometryData=function(geom){(1!==m_geomDataArray.length||m_geomDataArray[0]!==geom)&&(m_geomDataArray=[],m_geomDataArray.push(geom),this.modified())},this.geometryDataArray=function(){return m_geomDataArray},this.setGeometryDataArray=function(geoms){if(geoms instanceof Array){if(m_geomDataArray!==geoms)return m_geomDataArray=[],m_geomDataArray=geoms,this.modified(),!0}else console.log("[error] Requies array of geometry data");return!1},this.computeBounds=function(){if(null===m_geomDataArray||void 0===m_geomDataArray)return void this.resetBounds();var computeBoundsTimestamp=this.computeBoundsTimestamp(),boundsDirtyTimestamp=this.boundsDirtyTimestamp(),m_bounds=this.bounds(),geomBounds=null,i=null;if(boundsDirtyTimestamp.getMTime()>computeBoundsTimestamp.getMTime()){for(i=0;igeomBounds[0]&&(m_bounds[0]=geomBounds[0]),m_bounds[1]geomBounds[2]&&(m_bounds[2]=geomBounds[2]),m_bounds[3]geomBounds[4]&&(m_bounds[4]=geomBounds[4]),m_bounds[5]m_createMappersTimestamp.getMTime()){for(i=0;i0&&m_this.m_resetScene&&(m_this.resetCamera(),m_this.m_resetScene=!1),i=0;i=0&&sortedActors.push([actor.material().binNumber(),actor]);for(sortedActors.sort(function(a,b){return a[0]-b[0]}),i=0;idiagonals[1]?diagonals[0]>diagonals[2]?diagonals[0]/2:diagonals[2]/2:diagonals[1]>diagonals[2]?diagonals[1]/2:diagonals[2]/2,angle=aspect>=1?2*Math.atan(Math.tan(.5*angle)/aspect):2*Math.atan(Math.tan(.5*angle)*aspect),distance=radius/Math.sin(.5*angle),vup=m_this.m_camera.viewUpDirection(),Math.abs(vec3.dot(vup,vn))>.999&&m_this.m_camera.setViewUpDirection(-vup[2],vup[0],vup[1]),m_this.m_camera.setFocalPoint(center[0],center[1],center[2]),m_this.m_camera.setPosition(center[0]+distance*-vn[0],center[1]+distance*-vn[1],center[2]+distance*-vn[2]),m_this.resetCameraClippingRange(visibleBounds)},this.hasValidBounds=function(bounds){return bounds[0]===Number.MAX_VALUE||bounds[1]===-Number.MAX_VALUE||bounds[2]===Number.MAX_VALUE||bounds[3]===-Number.MAX_VALUE||bounds[4]===Number.MAX_VALUE||bounds[5]===-Number.MAX_VALUE?!1:!0},this.resetCameraClippingRange=function(bounds){if("undefined"==typeof bounds&&(m_this.m_camera.computeBounds(),bounds=m_this.m_camera.bounds()),m_this.hasValidBounds(bounds)){var vn=m_this.m_camera.viewPlaneNormal(),position=m_this.m_camera.position(),a=-vn[0],b=-vn[1],c=-vn[2],d=-(a*position[0]+b*position[1]+c*position[2]),range=vec2.create(),dist=null,i=null,j=null,k=null;if(m_this.m_resetClippingRange){for(range[0]=a*bounds[0]+b*bounds[2]+c*bounds[4]+d,range[1]=1e-18,k=0;2>k;k+=1)for(j=0;2>j;j+=1)for(i=0;2>i;i+=1)dist=a*bounds[i]+b*bounds[2+j]+c*bounds[4+k]+d,range[0]=distrange[1]?dist:range[1];range[0]<0&&(range[0]=0),range[0]=.99*range[0]-.5*(range[1]-range[0]),range[1]=1.01*range[1]+.5*(range[1]-range[0]),range[0]=range[0]>=range[1]?.01*range[1]:range[0],m_this.m_nearClippingPlaneTolerance||(m_this.m_nearClippingPlaneTolerance=.01,m_this.m_depthBits&&m_this.m_depthBits>16&&(m_this.m_nearClippingPlaneTolerance=.001)),range[0]x||0>y||0>=width||0>=height)return void console.log("[error] Invalid position and resize values",x,y,width,height);if(m_this.m_resizable&&(m_this.m_width=width,m_this.m_height=height,m_this.m_camera.setViewAspect(width/height),m_this.m_camera.setParallelExtents({width:width,height:height}),m_this.modified()),m_this.m_renderPasses)for(i=0;im_width||0===m_renderers[i].width()||m_renderers[i].height()>m_height||0===m_renderers[i].height())&&m_renderers[i].resize(m_x,m_y,m_width,m_height);return!0}catch(e){}return m_context||console("[ERROR] Unable to initialize WebGL. Your browser may not support it."),!1},this.context=function(){return m_context},this._cleanup=function(renderState){var i;for(i=0;iwidth?(m_currPos.x=0,m_outsideCanvas=!0):m_currPos.x=coords.x,coords.y<0||coords.y>height?(m_currPos.y=0,m_outsideCanvas=!0):m_currPos.y=coords.y,m_outsideCanvas!==!0?(fp=cam.focalPoint(),fwp=vec4.fromValues(fp[0],fp[1],fp[2],1),fdp=ren.worldToDisplay(fwp,cam.viewMatrix(),cam.projectionMatrix(),width,height),dp1=vec4.fromValues(m_currPos.x,m_currPos.y,fdp[2],1),dp2=vec4.fromValues(m_lastPos.x,m_lastPos.y,fdp[2],1),wp1=ren.displayToWorld(dp1,cam.viewMatrix(),cam.projectionMatrix(),width,height),wp2=ren.displayToWorld(dp2,cam.viewMatrix(),cam.projectionMatrix(),width,height),dx=wp1[0]-wp2[0],dy=wp1[1]-wp2[1],dz=wp1[2]-wp2[2],m_midMouseBtnDown&&(cam.pan(-dx,-dy,-dz),m_that.viewer().render()),m_leftMouseBtnDown&&(cam.rotate(m_lastPos.x-m_currPos.x,m_lastPos.y-m_currPos.y),ren.resetCameraClippingRange(),m_that.viewer().render()),m_rightMouseBtnDown&&(m_zTrans=2*(m_currPos.y-m_lastPos.y)/height,m_zTrans>0?cam.zoom(1-Math.abs(m_zTrans)):cam.zoom(1+Math.abs(m_zTrans)),ren.resetCameraClippingRange(),m_that.viewer().render()),m_lastPos.x=m_currPos.x,m_lastPos.y=m_currPos.y,!1):void 0},this.handleMouseDown=function(event){var coords;return 0===event.button&&(m_leftMouseBtnDown=!0),1===event.button&&(m_midMouseBtnDown=!0),2===event.button&&(m_rightMouseBtnDown=!0),coords=m_that.viewer().relMouseCoords(event),coords.x<0?m_lastPos.x=0:m_lastPos.x=coords.x,coords.y<0?m_lastPos.y=0:m_lastPos.y=coords.y,!1},this.handleMouseUp=function(event){return 0===event.button&&(m_leftMouseBtnDown=!1),1===event.button&&(m_midMouseBtnDown=!1),2===event.button&&(m_rightMouseBtnDown=!1),!1},this.handleMouseWheel=function(event){var ren=m_that.viewer().renderWindow().activeRenderer(),cam=ren.camera();return event.originalEvent.wheelDelta<0?cam.zoom(.9):cam.zoom(1.1),ren.resetCameraClippingRange(),m_that.viewer().render(),!0},this},inherit(vgl.trackballInteractorStyle,vgl.interactorStyle),vgl.pvwInteractorStyle=function(){"use strict";function render(){m_renderer.resetCameraClippingRange(),m_that.viewer().render()}if(!(this instanceof vgl.pvwInteractorStyle))return new vgl.pvwInteractorStyle;vgl.trackballInteractorStyle.call(this);var m_width,m_height,m_renderer,m_camera,m_outsideCanvas,m_coords,m_currentMousePos,m_focalPoint,m_focusWorldPt,m_focusDisplayPt,m_displayPt1,m_displayPt2,m_worldPt1,m_worldPt2,m_dx,m_dy,m_dz,m_zTrans,m_that=this,m_leftMouseButtonDown=!1,m_rightMouseButtonDown=!1,m_middleMouseButtonDown=!1,m_mouseLastPos={x:0,y:0};return this.handleMouseMove=function(event){var rens=[],i=null,secCameras=[],deltaxy=null;for(m_width=m_that.viewer().renderWindow().windowSize()[0],m_height=m_that.viewer().renderWindow().windowSize()[1],m_renderer=m_that.viewer().renderWindow().activeRenderer(),m_camera=m_renderer.camera(),m_outsideCanvas=!1,m_coords=m_that.viewer().relMouseCoords(event),m_currentMousePos={x:0,y:0},rens=m_that.viewer().renderWindow().renderers(),i=0;im_width?(m_currentMousePos.x=0,m_outsideCanvas=!0):m_currentMousePos.x=m_coords.x,m_coords.y<0||m_coords.y>m_height?(m_currentMousePos.y=0,m_outsideCanvas=!0):m_currentMousePos.y=m_coords.y,m_outsideCanvas!==!0){if(m_focalPoint=m_camera.focalPoint(),m_focusWorldPt=vec4.fromValues(m_focalPoint[0],m_focalPoint[1],m_focalPoint[2],1),m_focusDisplayPt=m_renderer.worldToDisplay(m_focusWorldPt,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_displayPt1=vec4.fromValues(m_currentMousePos.x,m_currentMousePos.y,m_focusDisplayPt[2],1),m_displayPt2=vec4.fromValues(m_mouseLastPos.x,m_mouseLastPos.y,m_focusDisplayPt[2],1),m_worldPt1=m_renderer.displayToWorld(m_displayPt1,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_worldPt2=m_renderer.displayToWorld(m_displayPt2,m_camera.viewMatrix(),m_camera.projectionMatrix(),m_width,m_height),m_dx=m_worldPt1[0]-m_worldPt2[0],m_dy=m_worldPt1[1]-m_worldPt2[1],m_dz=m_worldPt1[2]-m_worldPt2[2],m_middleMouseButtonDown&&(m_camera.pan(-m_dx,-m_dy,-m_dz),render()),m_leftMouseButtonDown){for(deltaxy=[m_mouseLastPos.x-m_currentMousePos.x,m_mouseLastPos.y-m_currentMousePos.y],m_camera.rotate(deltaxy[0],deltaxy[1]),i=0;i0?m_camera.zoom(1-Math.abs(m_zTrans)):m_camera.zoom(1+Math.abs(m_zTrans)),render()),m_mouseLastPos.x=m_currentMousePos.x,m_mouseLastPos.y=m_currentMousePos.y,!1}},this.handleMouseDown=function(event){return 0===event.button&&(m_leftMouseButtonDown=!0),1===event.button&&(m_middleMouseButtonDown=!0),2===event.button&&(m_rightMouseButtonDown=!0),m_coords=m_that.viewer().relMouseCoords(event),m_coords.x<0?m_mouseLastPos.x=0:m_mouseLastPos.x=m_coords.x,m_coords.y<0?m_mouseLastPos.y=0:m_mouseLastPos.y=m_coords.y,!1},this.handleMouseUp=function(event){return 0===event.button&&(m_leftMouseButtonDown=!1),1===event.button&&(m_middleMouseButtonDown=!1),2===event.button&&(m_rightMouseButtonDown=!1),!1},this},inherit(vgl.pvwInteractorStyle,vgl.trackballInteractorStyle),vgl.viewer=function(canvas,options){"use strict";if(!(this instanceof vgl.viewer))return new vgl.viewer(canvas,options);vgl.object.call(this);var m_that=this,m_canvas=canvas,m_ready=!0,m_interactorStyle=null,m_renderer=vgl.renderer(options),m_renderWindow=vgl.renderWindow(m_canvas);return this.canvas=function(){return m_canvas},this.renderWindow=function(){return m_renderWindow},this.init=function(){null!==m_renderWindow?m_renderWindow._setup():console.log("[ERROR] No render window attached")},this.exit=function(renderState){null!==m_renderWindow?m_renderWindow._cleanup(renderState):console.log("[ERROR] No render window attached")},this.interactorStyle=function(){return m_interactorStyle},this.setInteractorStyle=function(style){style!==m_interactorStyle&&(m_interactorStyle=style,m_interactorStyle.setViewer(this),this.modified())},this.handleMouseDown=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);2===event.button&&fixedEvent.preventDefault(),fixedEvent.state="down",fixedEvent.type=vgl.event.mousePress,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseUp=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.state="up",fixedEvent.type=vgl.event.mouseRelease,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseMove=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseMove,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseWheel=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseWheel,$(m_that).trigger(fixedEvent)}return!0},this.handleMouseOut=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.mouseOut,$(m_that).trigger(fixedEvent)}return!0},this.handleKeyPress=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.keyPress,$(m_that).trigger(fixedEvent)}return!0},this.handleContextMenu=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.contextMenu,$(m_that).trigger(fixedEvent)}return!1},this.handleClick=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.click,$(m_that).trigger(fixedEvent)}return!1},this.handleDoubleClick=function(event){if(m_ready===!0){var fixedEvent=$.event.fix(event||window.event);fixedEvent.preventDefault(),fixedEvent.type=vgl.event.dblClick,$(m_that).trigger(fixedEvent)}return!1},this.relMouseCoords=function(event){if(void 0===event.pageX||void 0===event.pageY)throw"Missing attributes pageX and pageY on the event";var totalOffsetX=0,totalOffsetY=0,canvasX=0,canvasY=0,currentElement=m_canvas;do totalOffsetX+=currentElement.offsetLeft-currentElement.scrollLeft,totalOffsetY+=currentElement.offsetTop-currentElement.scrollTop,currentElement=currentElement.offsetParent;while(currentElement);return canvasX=event.pageX-totalOffsetX,canvasY=event.pageY-totalOffsetY,{x:canvasX,y:canvasY}},this.render=function(){m_renderWindow.render()},this.bindEventHandlers=function(){$(m_canvas).on("mousedown",this.handleMouseDown),$(m_canvas).on("mouseup",this.handleMouseUp),$(m_canvas).on("mousemove",this.handleMouseMove),$(m_canvas).on("mousewheel",this.handleMouseWheel),$(m_canvas).on("contextmenu",this.handleContextMenu)},this.unbindEventHandlers=function(){$(m_canvas).off("mousedown",this.handleMouseDown),$(m_canvas).off("mouseup",this.handleMouseUp),$(m_canvas).off("mousemove",this.handleMouseMove),$(m_canvas).off("mousewheel",this.handleMouseWheel),$(m_canvas).off("contextmenu",this.handleContextMenu)},this._init=function(){this.bindEventHandlers(),m_renderWindow.addRenderer(m_renderer)},this._init(),this},inherit(vgl.viewer,vgl.object),vgl.shader=function(type){"use strict";if(!(this instanceof vgl.shader))return new vgl.shader(type);vgl.object.call(this);var m_shaderHandle=null,m_compileTimestamp=vgl.timestamp(),m_shaderType=type,m_shaderSource="";this.shaderHandle=function(){return m_shaderHandle},this.shaderType=function(){return m_shaderType},this.shaderSource=function(){return m_shaderSource},this.setShaderSource=function(source){m_shaderSource=source,this.modified()},this.compile=function(renderState){return this.getMTime()-1)return!1;var i;for(i=0;i-1?!1:(m_uniforms.push(uniform),void m_this.modified())},this.addVertexAttribute=function(attr,key){m_vertexAttributes[key]=attr,m_this.modified()},this.uniformLocation=function(name){return m_uniformNameToLocation[name]},this.attributeLocation=function(name){return m_vertexAttributeNameToLocation[name]},this.uniform=function(name){var i;for(i=0;i=this.getMTime())){for(m_this._setup(renderState),i=0;im_setupTimestamp.getMTime()&&this.setup(renderState),activateTextureUnit(renderState),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,this.m_textureHandle)},this.undoBind=function(renderState){renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,null)},this.image=function(){return this.m_image},this.setImage=function(image){return null!==image?(this.m_image=image,this.updateDimensions(),this.modified(),!0):!1},this.nearestPixel=function(){return this.m_nearestPixel},this.setNearestPixel=function(nearest){return nearest=nearest?!0:!1,nearest!==this.m_nearestPixel?(this.m_nearestPixel=nearest,this.modified(),!0):!1},this.textureUnit=function(){return this.m_textureUnit},this.setTextureUnit=function(unit){return this.m_textureUnit===unit?!1:(this.m_textureUnit=unit,this.modified(),!0)},this.width=function(){return this.m_width},this.setWidth=function(width){return m_that.m_width!==width?(m_that.m_width=width,m_that.modified(),!0):!1},this.height=function(){return m_that.m_height},this.setHeight=function(height){return m_that.m_height!==height?(m_that.m_height=height,m_that.modified(),!0):!1},this.depth=function(){return this.m_depth},this.setDepth=function(depth){return null===this.m_image?!1:(this.m_depth=depth,this.modified(),!0)},this.textureHandle=function(){return this.m_textureHandle},this.internalFormat=function(){return this.m_internalFormat},this.setInternalFormat=function(internalFormat){return this.m_internalFormat!==internalFormat?(this.m_internalFormat=internalFormat,this.modified(),!0):!1},this.pixelFormat=function(){return this.m_pixelFormat},this.setPixelFormat=function(pixelFormat){return null===this.m_image?!1:(this.m_pixelFormat=pixelFormat,this.modified(),!0)},this.pixelDataType=function(){return this.m_pixelDataType},
+this.setPixelDataType=function(pixelDataType){return null===this.m_image?!1:(this.m_pixelDataType=pixelDataType,this.modified(),!0)},this.computeInternalFormatUsingImage=function(){this.m_internalFormat=vgl.GL.RGBA,this.m_pixelFormat=vgl.GL.RGBA,this.m_pixelDataType=vgl.GL.UNSIGNED_BYTE},this.updateDimensions=function(){null!==this.m_image&&(this.m_width=this.m_image.width,this.m_height=this.m_image.height,this.m_depth=0)},this},inherit(vgl.texture,vgl.materialAttribute),vgl.lookupTable=function(){"use strict";if(!(this instanceof vgl.lookupTable))return new vgl.lookupTable;vgl.texture.call(this);var m_setupTimestamp=vgl.timestamp(),m_range=[0,0];return this.m_colorTable=[.07514311,.468049805,1,1,.247872569,.498782363,1,1,.339526309,.528909511,1,1,.409505078,.558608486,1,1,.468487184,.588057293,1,1,.520796675,.617435078,1,1,.568724526,.646924167,1,1,.613686735,.676713218,1,1,.656658579,.707001303,1,1,.698372844,.738002964,1,1,.739424025,.769954435,1,1,.780330104,.803121429,1,1,.821573924,.837809045,1,1,.863634967,.874374691,1,1,.907017747,.913245283,1,1,.936129275,.938743558,.983038586,1,.943467973,.943498599,.943398095,1,.990146732,.928791426,.917447482,1,1,.88332677,.861943246,1,1,.833985467,.803839606,1,1,.788626485,.750707739,1,1,.746206642,.701389973,1,1,.70590052,.654994046,1,1,.667019783,.610806959,1,1,.6289553,.568237474,1,1,.591130233,.526775617,1,1,.552955184,.485962266,1,1,.513776083,.445364274,1,1,.472800903,.404551679,1,1,.428977855,.363073592,1,1,.380759558,.320428137,1,.961891484,.313155629,.265499262,1,.916482116,.236630659,.209939162,1].map(function(x){return 255*x}),this.setup=function(renderState){0===this.textureUnit()?renderState.m_context.activeTexture(vgl.GL.TEXTURE0):1===this.textureUnit()&&renderState.m_context.activeTexture(vgl.GL.TEXTURE1),renderState.m_context.deleteTexture(this.m_textureHandle),this.m_textureHandle=renderState.m_context.createTexture(),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,this.m_textureHandle),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_MIN_FILTER,vgl.GL.LINEAR),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_MAG_FILTER,vgl.GL.LINEAR),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_WRAP_S,vgl.GL.CLAMP_TO_EDGE),renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,vgl.GL.TEXTURE_WRAP_T,vgl.GL.CLAMP_TO_EDGE),renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT,1),this.m_width=this.m_colorTable.length/4,this.m_height=1,this.m_depth=0,renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D,0,vgl.GL.RGBA,this.m_width,this.m_height,this.m_depth,vgl.GL.RGBA,vgl.GL.UNSIGNED_BYTE,new Uint8Array(this.m_colorTable)),renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D,null),m_setupTimestamp.modified()},this.colorTable=function(){return this.m_colorTable},this.setColorTable=function(colors){return this.m_colorTable===colors?!1:(this.m_colorTable=colors,this.modified(),!0)},this.range=function(){return m_range},this.setRange=function(range){return m_range===range?!1:(m_range=range,this.modified(),!0)},this.updateRange=function(range){range instanceof Array||console.log("[error] Invalid data type for range. Requires array [min,max]"),range[0]m_range[1]&&(m_range[1]=range[1],this.modified())},this},inherit(vgl.lookupTable,vgl.texture),vgl.uniform=function(type,name){"use strict";if(!(this instanceof vgl.uniform))return new vgl.uniform;this.getTypeNumberOfComponents=function(type){switch(type){case vgl.GL.FLOAT:case vgl.GL.INT:case vgl.GL.BOOL:return 1;case vgl.GL.FLOAT_VEC2:case vgl.GL.INT_VEC2:case vgl.GL.BOOL_VEC2:return 2;case vgl.GL.FLOAT_VEC3:case vgl.GL.INT_VEC3:case vgl.GL.BOOL_VEC3:return 3;case vgl.GL.FLOAT_VEC4:case vgl.GL.INT_VEC4:case vgl.GL.BOOL_VEC4:return 4;case vgl.GL.FLOAT_MAT3:return 9;case vgl.GL.FLOAT_MAT4:return 16;default:return 0}};var m_type=type,m_name=name,m_dataArray=[];return m_dataArray.length=this.getTypeNumberOfComponents(m_type),this.name=function(){return m_name},this.type=function(){return m_type},this.get=function(){return m_dataArray},this.set=function(value){var i=0;if(16===m_dataArray.length)for(i=0;16>i;i+=1)m_dataArray[i]=value[i];else if(9===m_dataArray.length)for(i=0;9>i;i+=1)m_dataArray[i]=value[i];else if(4===m_dataArray.length)for(i=0;4>i;i+=1)m_dataArray[i]=value[i];else if(3===m_dataArray.length)for(i=0;3>i;i+=1)m_dataArray[i]=value[i];else if(2===m_dataArray.length)for(i=0;2>i;i+=1)m_dataArray[i]=value[i];else m_dataArray[0]=value},this.callGL=function(renderState,location){if(!(this.m_numberElements<1))switch(m_type){case vgl.GL.BOOL:case vgl.GL.INT:renderState.m_context.uniform1iv(location,m_dataArray);break;case vgl.GL.FLOAT:renderState.m_context.uniform1fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC2:renderState.m_context.uniform2fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC3:renderState.m_context.uniform3fv(location,m_dataArray);break;case vgl.GL.FLOAT_VEC4:renderState.m_context.uniform4fv(location,m_dataArray);break;case vgl.GL.FLOAT_MAT3:renderState.m_context.uniformMatrix3fv(location,vgl.GL.FALSE,m_dataArray);break;case vgl.GL.FLOAT_MAT4:renderState.m_context.uniformMatrix4fv(location,vgl.GL.FALSE,m_dataArray)}},this.update=function(renderState,program){renderState=renderState,program=program},this},vgl.modelViewUniform=function(name){"use strict";return this instanceof vgl.modelViewUniform?(0===name.length&&(name="modelViewMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_modelViewMatrix)},this):new vgl.modelViewUniform(name)},inherit(vgl.modelViewUniform,vgl.uniform),vgl.modelViewOriginUniform=function(name,origin){"use strict";if(!(this instanceof vgl.modelViewOriginUniform))return new vgl.modelViewOriginUniform(name,origin);0===name.length&&(name="modelViewMatrix");var m_origin=vec3.fromValues(origin[0],origin[1],origin[2]);return vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program;var view=mat4.create();if(mat4.translate(view,renderState.m_modelViewMatrix,m_origin),renderState.m_modelViewAlignment){var align=renderState.m_modelViewAlignment;view[12]=Math.round(view[12]/align.roundx)*align.roundx+align.dx,view[13]=Math.round(view[13]/align.roundy)*align.roundy+align.dy}this.set(view)},this},inherit(vgl.modelViewOriginUniform,vgl.uniform),vgl.projectionUniform=function(name){"use strict";return this instanceof vgl.projectionUniform?(0===name.length&&(name="projectionMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_projectionMatrix)},this):new vgl.projectionUniform(name)},inherit(vgl.projectionUniform,vgl.uniform),vgl.floatUniform=function(name,value){"use strict";return this instanceof vgl.floatUniform?(0===name.length&&(name="floatUniform"),value=void 0===value?1:value,vgl.uniform.call(this,vgl.GL.FLOAT,name),void this.set(value)):new vgl.floatUniform(name,value)},inherit(vgl.floatUniform,vgl.uniform),vgl.normalMatrixUniform=function(name){"use strict";return this instanceof vgl.normalMatrixUniform?(0===name.length&&(name="normalMatrix"),vgl.uniform.call(this,vgl.GL.FLOAT_MAT4,name),this.set(mat4.create()),this.update=function(renderState,program){program=program,this.set(renderState.m_normalMatrix)},this):new vgl.normalMatrixUniform(name)},inherit(vgl.normalMatrixUniform,vgl.uniform),vgl.vertexAttributeKeys={Position:0,Normal:1,TextureCoordinate:2,Color:3,Scalar:4,CountAttributeIndex:5},vgl.vertexAttributeKeysIndexed={Zero:0,One:1,Two:2,Three:3,Four:4,Five:5,Six:6,Seven:7,Eight:8,Nine:9},vgl.vertexAttribute=function(name){"use strict";if(!(this instanceof vgl.vertexAttribute))return new vgl.vertexAttribute(name);var m_name=name;this.name=function(){return m_name},this.bindVertexData=function(renderState,key){var geometryData=renderState.m_mapper.geometryData(),sourceData=geometryData.sourceData(key),program=renderState.m_material.shaderProgram();renderState.m_context.vertexAttribPointer(program.attributeLocation(m_name),sourceData.attributeNumberOfComponents(key),sourceData.attributeDataType(key),sourceData.normalized(key),sourceData.attributeStride(key),sourceData.attributeOffset(key)),renderState.m_context.enableVertexAttribArray(program.attributeLocation(m_name))},this.undoBindVertexData=function(renderState,key){key=key;var program=renderState.m_material.shaderProgram();renderState.m_context.disableVertexAttribArray(program.attributeLocation(m_name))}},vgl.source=function(){"use strict";return this instanceof vgl.source?(vgl.object.call(this),this.create=function(){},this):new vgl.source},inherit(vgl.source,vgl.object),vgl.planeSource=function(){"use strict";if(!(this instanceof vgl.planeSource))return new vgl.planeSource;vgl.source.call(this);var m_origin=[0,0,0],m_point1=[1,0,0],m_point2=[0,1,0],m_normal=[0,0,1],m_xresolution=1,m_yresolution=1,m_geom=null;this.setOrigin=function(x,y,z){m_origin[0]=x,m_origin[1]=y,m_origin[2]=z},this.setPoint1=function(x,y,z){m_point1[0]=x,m_point1[1]=y,m_point1[2]=z},this.setPoint2=function(x,y,z){m_point2[0]=x,m_point2[1]=y,m_point2[2]=z},this.create=function(){m_geom=new vgl.geometryData;var i,j,k,ii,numPts,numPolys,sourceTexCoords,x=[],tc=[],v1=[],v2=[],pts=[],posIndex=0,normIndex=0,colorIndex=0,texCoordIndex=0,positions=[],normals=[],colors=[],texCoords=[],indices=[],tristrip=null,sourcePositions=null,sourceColors=null;for(x.length=3,tc.length=2,v1.length=3,v2.length=3,pts.length=3,i=0;3>i;i+=1)v1[i]=m_point1[i]-m_origin[i],v2[i]=m_point2[i]-m_origin[i];for(numPts=(m_xresolution+1)*(m_yresolution+1),numPolys=m_xresolution*m_yresolution*2,positions.length=3*numPts,normals.length=3*numPts,texCoords.length=2*numPts,indices.length=numPts,k=0,i=0;m_yresolution+1>i;i+=1)for(tc[1]=i/m_yresolution,j=0;m_xresolution+1>j;j+=1){for(tc[0]=j/m_xresolution,ii=0;3>ii;ii+=1)x[ii]=m_origin[ii]+tc[0]*v1[ii]+tc[1]*v2[ii];positions[posIndex++]=x[0],positions[posIndex++]=x[1],positions[posIndex++]=x[2],colors[colorIndex++]=1,colors[colorIndex++]=1,colors[colorIndex++]=1,normals[normIndex++]=m_normal[0],normals[normIndex++]=m_normal[1],normals[normIndex++]=m_normal[2],texCoords[texCoordIndex++]=tc[0],texCoords[texCoordIndex++]=tc[1]}for(i=0;m_yresolution>i;i+=1)for(j=0;m_xresolution>j;j+=1)pts[0]=j+i*(m_xresolution+1),pts[1]=pts[0]+1,pts[2]=pts[0]+m_xresolution+2,pts[3]=pts[0]+m_xresolution+1;for(i=0;numPts>i;i+=1)indices[i]=i;return tristrip=new vgl.triangleStrip,tristrip.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(positions),sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(colors),sourceTexCoords=vgl.sourceDataT2fv(),sourceTexCoords.pushBack(texCoords),m_geom.addSource(sourcePositions),m_geom.addSource(sourceColors),m_geom.addSource(sourceTexCoords),m_geom.addPrimitive(tristrip),m_geom}},inherit(vgl.planeSource,vgl.source),vgl.pointSource=function(){"use strict";if(!(this instanceof vgl.pointSource))return new vgl.pointSource;vgl.source.call(this);var m_this=this,m_positions=[],m_colors=[],m_textureCoords=[],m_size=[],m_geom=null;this.getPositions=function(){return m_positions},this.setPositions=function(positions){positions instanceof Array?m_positions=positions:console.log("[ERROR] Invalid data type for positions. Array is required."),m_this.modified()},this.getColors=function(){return m_colors},this.setColors=function(colors){colors instanceof Array?m_colors=colors:console.log("[ERROR] Invalid data type for colors. Array is required."),m_this.modified()},this.getSize=function(){return m_size},this.setSize=function(size){m_size=size,this.modified()},this.setTextureCoordinates=function(texcoords){texcoords instanceof Array?m_textureCoords=texcoords:console.log("[ERROR] Invalid data type for texture coordinates. Array is required."),m_this.modified()},this.create=function(){if(m_geom=new vgl.geometryData,m_positions.length%3!==0)return void console.log("[ERROR] Invalid length of the points array");var pointsPrimitive,sourcePositions,sourceColors,sourceTexCoords,sourceSize,numPts=m_positions.length/3,i=0,indices=[];for(indices.length=numPts,i=0;numPts>i;i+=1)indices[i]=i;if(sourceSize=vgl.sourceDataDf(),numPts!==m_size.length)for(i=0;numPts>i;i+=1)sourceSize.pushBack(m_size);else sourceSize.setData(m_size);return m_geom.addSource(sourceSize),pointsPrimitive=new vgl.points,pointsPrimitive.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(m_positions),m_geom.addSource(sourcePositions),m_colors.length>0&&m_colors.length===m_positions.length?(sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(m_colors),m_geom.addSource(sourceColors)):m_colors.length>0&&m_colors.length!==m_positions.length&&console.log("[ERROR] Number of colors are different than number of points"),m_textureCoords.length>0&&m_textureCoords.length===m_positions.length?(sourceTexCoords=vgl.sourceDataT2fv(),sourceTexCoords.pushBack(m_textureCoords),m_geom.addSource(sourceTexCoords)):m_textureCoords.length>0&&m_textureCoords.length/2!==m_positions.length/3&&console.log("[ERROR] Number of texture coordinates are different than number of points"),m_geom.addPrimitive(pointsPrimitive),m_geom}},inherit(vgl.pointSource,vgl.source),vgl.lineSource=function(positions,colors){"use strict";if(!(this instanceof vgl.lineSource))return new vgl.lineSource;vgl.source.call(this);var m_positions=positions,m_colors=colors;this.setPositions=function(positions){return positions instanceof Array?(m_positions=positions,this.modified(),!0):(console.log("[ERROR] Invalid data type for positions. Array is required."),!1)},this.setColors=function(colors){return colors instanceof Array?(m_colors=colors,this.modified(),!0):(console.log("[ERROR] Invalid data type for colors. Array is required."),!1)},this.create=function(){if(!m_positions)return void console.log("[error] Invalid positions");if(m_positions.length%3!==0)return void console.log("[error] Line source requires 3d points");if(m_positions.length%3!==0)return void console.log("[ERROR] Invalid length of the points array");var i,linesPrimitive,sourcePositions,sourceColors,m_geom=new vgl.geometryData,numPts=m_positions.length/3,indices=[];for(indices.length=numPts,i=0;numPts>i;i+=1)indices[i]=i;return linesPrimitive=new vgl.lines,linesPrimitive.setIndices(indices),sourcePositions=vgl.sourceDataP3fv(),sourcePositions.pushBack(m_positions),m_geom.addSource(sourcePositions),m_colors&&m_colors.length>0&&m_colors.length===m_positions.length?(sourceColors=vgl.sourceDataC3fv(),sourceColors.pushBack(m_colors),m_geom.addSource(sourceColors)):m_colors&&m_colors.length>0&&m_colors.length!==m_positions.length&&console.log("[error] Number of colors are different than number of points"),m_geom.addPrimitive(linesPrimitive),m_geom}},inherit(vgl.lineSource,vgl.source),vgl.utils=function(){"use strict";return this instanceof vgl.utils?(vgl.object.call(this),this):new vgl.utils},inherit(vgl.utils,vgl.object),vgl.utils.computePowerOfTwo=function(value,pow){"use strict";for(pow=pow||1;value>pow;)pow*=2;return pow},vgl.utils.createTextureVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 textureCoord;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iTextureCoord = textureCoord;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createTextureFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying highp vec3 iTextureCoord;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyz, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createRgbaTextureFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying highp vec3 iTextureCoord;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {"," mediump vec4 color = vec4(texture2D(sampler2d, vec2(iTextureCoord.s, iTextureCoord.t)).xyzw);"," color.w *= opacity;"," gl_FragColor = color;","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying mediump vec3 iVertexColor;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPointVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","attribute float vertexSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying mediump vec3 iVertexColor;","varying highp vec3 iTextureCoord;","void main(void)","{","gl_PointSize = vertexSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createVertexShaderSolidColor=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createVertexShaderColorMap=function(context,min,max){"use strict";context=context,min=min,max=max;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute float vertexScalar;","uniform mediump float pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float lutMin;","uniform float lutMax;","varying mediump float iVertexScalar;","void main(void)","{","gl_PointSize = pointSize;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);","iVertexScalar = (vertexScalar-lutMin)/(lutMax-lutMin);","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump vec3 iVertexColor;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(iVertexColor, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createPhongVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute highp vec3 vertexPosition;","attribute mediump vec3 vertexNormal;","attribute mediump vec3 vertexColor;","uniform highp mat4 projectionMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 normalMatrix;","varying highp vec4 varPosition;","varying mediump vec3 varNormal;","varying mediump vec3 varVertexColor;","void main(void)","{","varPosition = modelViewMatrix * vec4(vertexPosition, 1.0);","gl_Position = projectionMatrix * varPosition;","varNormal = vec3(normalMatrix * vec4(vertexNormal, 0.0));","varVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPhongFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["uniform mediump float opacity;","precision mediump float;","varying vec3 varNormal;","varying vec4 varPosition;","varying mediump vec3 varVertexColor;","const vec3 lightPos = vec3(0.0, 0.0,10000.0);","const vec3 ambientColor = vec3(0.01, 0.01, 0.01);","const vec3 specColor = vec3(0.0, 0.0, 0.0);","void main() {","vec3 normal = normalize(varNormal);","vec3 lightDir = normalize(lightPos);","vec3 reflectDir = -reflect(lightDir, normal);","vec3 viewDir = normalize(-varPosition.xyz);","float lambertian = max(dot(lightDir, normal), 0.0);","vec3 color = vec3(0.0);","if(lambertian > 0.0) {"," color = lambertian * varVertexColor;","}","gl_FragColor = vec4(color * opacity, 1.0 - opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createFragmentShaderSolidColor=function(context,color){"use strict";var fragmentShaderSource=["uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4("+color[0]+","+color[1]+","+color[2]+", opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createFragmentShaderColorMap=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump float iVertexScalar;","uniform sampler2D sampler2d;","uniform mediump float opacity;","void main(void) {","gl_FragColor = vec4(texture2D(sampler2d, vec2(iVertexScalar, 0.0)).xyz, opacity);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createPointSpritesVertexShader=function(context){"use strict";context=context;var vertexShaderSource=["attribute vec3 vertexPosition;","attribute vec3 vertexColor;","uniform mediump vec2 pointSize;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float height;","varying mediump vec3 iVertexColor;","varying highp float iVertexScalar;","void main(void)","{","mediump float realPointSize = pointSize.y;","if (pointSize.x > pointSize.y) {"," realPointSize = pointSize.x;}","gl_PointSize = realPointSize ;","iVertexScalar = vertexPosition.z;","gl_Position = projectionMatrix * modelViewMatrix * vec4(vertexPosition.xy, height, 1.0);"," iVertexColor = vertexColor;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader},vgl.utils.createPointSpritesFragmentShader=function(context){"use strict";context=context;var fragmentShaderSource=["varying mediump vec3 iVertexColor;","varying highp float iVertexScalar;","uniform sampler2D opacityLookup;","uniform highp float lutMin;","uniform highp float lutMax;","uniform sampler2D scalarsToColors;","uniform int useScalarsToColors;","uniform int useVertexColors;","uniform mediump vec2 pointSize;","uniform mediump float vertexColorWeight;","void main(void) {","mediump vec2 realTexCoord;","if (pointSize.x > pointSize.y) {"," realTexCoord = vec2(1.0, pointSize.y/pointSize.x) * gl_PointCoord;","} else {"," realTexCoord = vec2(pointSize.x/pointSize.y, 1.0) * gl_PointCoord;","}","highp float texOpacity = texture2D(opacityLookup, realTexCoord).w;","if (useScalarsToColors == 1) {"," gl_FragColor = vec4(texture2D(scalarsToColors, vec2((iVertexScalar - lutMin)/(lutMax - lutMin), 0.0)).xyz, texOpacity);","} else if (useVertexColors == 1) {"," gl_FragColor = vec4(iVertexColor, texOpacity);","} else {"," gl_FragColor = vec4(texture2D(opacityLookup, realTexCoord).xyz, texOpacity);","}}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader},vgl.utils.createTextureMaterial=function(isRgba,origin){"use strict";var modelViewUniform,mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createTextureVertexShader(vgl.GL),fragmentShader=null,posVertAttr=new vgl.vertexAttribute("vertexPosition"),texCoordVertAttr=new vgl.vertexAttribute("textureCoord"),pointsizeUniform=new vgl.floatUniform("pointSize",5),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"sampler2d"),opacityUniform=null;return modelViewUniform=void 0!==origin?new vgl.modelViewOriginUniform("modelViewMatrix",origin):new vgl.modelViewUniform("modelViewMatrix"),samplerUniform.set(0),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(texCoordVertAttr,vgl.vertexAttributeKeys.TextureCoordinate),prog.addUniform(pointsizeUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),fragmentShader=isRgba?vgl.utils.createRgbaTextureFragmentShader(vgl.GL):vgl.utils.createTextureFragmentShader(vgl.GL),opacityUniform=new vgl.floatUniform("opacity",1),prog.addUniform(opacityUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createGeometryMaterial=function(){"use strict";var mat=new vgl.material,prog=new vgl.shaderProgram,pointSize=5,opacity=1,vertexShader=vgl.utils.createVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),pointsizeUniform=new vgl.floatUniform("pointSize",pointSize),opacityUniform=new vgl.floatUniform("opacity",opacity),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat},vgl.utils.createPointGeometryMaterial=function(opacity){"use strict";opacity=void 0===opacity?1:opacity;var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPointVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),sizeVertAttr=new vgl.vertexAttribute("vertexSize"),opacityUniform=new vgl.floatUniform("opacity",opacity),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addVertexAttribute(sizeVertAttr,vgl.vertexAttributeKeys.Scalar),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createPhongMaterial=function(){"use strict";var mat=new vgl.material,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPhongVertexShader(vgl.GL),fragmentShader=vgl.utils.createPhongFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),normalVertAttr=new vgl.vertexAttribute("vertexNormal"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),normalUniform=new vgl.normalMatrixUniform("normalMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(normalVertAttr,vgl.vertexAttributeKeys.Normal),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addUniform(normalUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat},vgl.utils.createColorMaterial=function(){"use strict";var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShader(vgl.GL),fragmentShader=vgl.utils.createFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),texCoordVertAttr=new vgl.vertexAttribute("textureCoord"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addVertexAttribute(texCoordVertAttr,vgl.vertexAttributeKeys.TextureCoordinate),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createColorMappedMaterial=function(lut){"use strict";lut||(lut=new vgl.lookupTable);var scalarRange=lut.range(),mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShaderColorMap(vgl.GL,scalarRange[0],scalarRange[1]),fragmentShader=vgl.utils.createFragmentShaderColorMap(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),scalarVertAttr=new vgl.vertexAttribute("vertexScalar"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),lutMinUniform=new vgl.floatUniform("lutMin",scalarRange[0]),lutMaxUniform=new vgl.floatUniform("lutMax",scalarRange[1]),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.FLOAT,"sampler2d"),lookupTable=lut;return samplerUniform.set(0),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(scalarVertAttr,vgl.vertexAttributeKeys.Scalar),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(lutMinUniform),prog.addUniform(lutMaxUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat.addAttribute(lookupTable),mat},vgl.utils.updateColorMappedMaterial=function(mat,lut){"use strict";if(!mat)return void console.log("[warning] Invalid material. Nothing to update.");if(!lut)return void console.log("[warning] Invalid lookup table. Nothing to update.");var lutMin=mat.shaderProgram().uniform("lutMin"),lutMax=mat.shaderProgram().uniform("lutMax");lutMin.set(lut.range()[0]),lutMax.set(lut.range()[1]),mat.setAttribute(lut)},vgl.utils.createSolidColorMaterial=function(color){"use strict";color||(color=[1,1,1]);var mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createVertexShaderSolidColor(vgl.GL),fragmentShader=vgl.utils.createFragmentShaderSolidColor(vgl.GL,color),posVertAttr=new vgl.vertexAttribute("vertexPosition"),pointsizeUniform=new vgl.floatUniform("pointSize",5),opacityUniform=new vgl.floatUniform("opacity",1),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix");return prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addUniform(pointsizeUniform),prog.addUniform(opacityUniform),prog.addUniform(modelViewUniform),
+prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),mat},vgl.utils.createPointSpritesMaterial=function(image,lut){"use strict";var scalarRange=void 0===lut?[0,1]:lut.range(),mat=new vgl.material,blend=new vgl.blend,prog=new vgl.shaderProgram,vertexShader=vgl.utils.createPointSpritesVertexShader(vgl.GL),fragmentShader=vgl.utils.createPointSpritesFragmentShader(vgl.GL),posVertAttr=new vgl.vertexAttribute("vertexPosition"),colorVertAttr=new vgl.vertexAttribute("vertexColor"),heightUniform=new vgl.floatUniform("height",0),vertexColorWeightUniform=new vgl.floatUniform("vertexColorWeight",0),lutMinUniform=new vgl.floatUniform("lutMin",scalarRange[0]),lutMaxUniform=new vgl.floatUniform("lutMax",scalarRange[1]),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"opacityLookup"),scalarsToColors=new vgl.uniform(vgl.GL.INT,"scalarsToColors"),useScalarsToColors=new vgl.uniform(vgl.GL.INT,"useScalarsToColors"),useVertexColors=new vgl.uniform(vgl.GL.INT,"useVertexColors"),pointSize=new vgl.uniform(vgl.GL.FLOAT_VEC2,"pointSize"),texture=new vgl.texture;return samplerUniform.set(0),scalarsToColors.set(1),useScalarsToColors.set(0),useVertexColors.set(0),pointSize.set([1,1]),prog.addVertexAttribute(posVertAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(colorVertAttr,vgl.vertexAttributeKeys.Color),prog.addUniform(heightUniform),prog.addUniform(vertexColorWeightUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addUniform(samplerUniform),prog.addUniform(useVertexColors),prog.addUniform(useScalarsToColors),prog.addUniform(pointSize),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),lut&&(prog.addUniform(scalarsToColors),useScalarsToColors.set(1),prog.addUniform(lutMinUniform),prog.addUniform(lutMaxUniform),lut.setTextureUnit(1),mat.addAttribute(lut)),texture.setImage(image),texture.setTextureUnit(0),mat.addAttribute(texture),mat},vgl.utils.createPlane=function(originX,originY,originZ,point1X,point1Y,point1Z,point2X,point2Y,point2Z){"use strict";var mapper=new vgl.mapper,planeSource=new vgl.planeSource,mat=vgl.utils.createGeometryMaterial(),actor=new vgl.actor;return planeSource.setOrigin(originX,originY,originZ),planeSource.setPoint1(point1X,point1Y,point1Z),planeSource.setPoint2(point2X,point2Y,point2Z),mapper.setGeometryData(planeSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createTexturePlane=function(originX,originY,originZ,point1X,point1Y,point1Z,point2X,point2Y,point2Z,isRgba){"use strict";var mapper=new vgl.mapper,planeSource=new vgl.planeSource,mat=vgl.utils.createTextureMaterial(isRgba,[originX,originY,originZ]),actor=new vgl.actor;return planeSource.setPoint1(point1X-originX,point1Y-originY,point1Z-originZ),planeSource.setPoint2(point2X-originX,point2Y-originY,point2Z-originZ),mapper.setGeometryData(planeSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createPoints=function(positions,size,colors,texcoords,opacity){"use strict";if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;opacity=void 0===opacity?1:opacity;var mapper=new vgl.mapper,pointSource=new vgl.pointSource,mat=vgl.utils.createPointGeometryMaterial(opacity),actor=new vgl.actor;return pointSource.setPositions(positions),colors&&pointSource.setColors(colors),texcoords&&pointSource.setTextureCoordinates(texcoords),size?pointSource.setSize(size):pointSource.setSize(1),mapper.setGeometryData(pointSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createPointSprites=function(image,positions,colors,texcoords){"use strict";if(!image)return console.log("[ERROR] Point sprites requires an image"),null;if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;var mapper=new vgl.mapper,pointSource=new vgl.pointSource,mat=vgl.utils.createPointSpritesMaterial(image),actor=new vgl.actor;return pointSource.setPositions(positions),colors&&pointSource.setColors(colors),texcoords&&pointSource.setTextureCoordinates(texcoords),mapper.setGeometryData(pointSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createLines=function(positions,colors){"use strict";if(!positions)return console.log("[ERROR] Cannot create points without positions"),null;var mapper=new vgl.mapper,lineSource=new vgl.lineSource,mat=vgl.utils.createGeometryMaterial(),actor=new vgl.actor;return lineSource.setPositions(positions),colors&&lineSource.setColors(colors),mapper.setGeometryData(lineSource.create()),actor.setMapper(mapper),actor.setMaterial(mat),actor},vgl.utils.createColorLegend=function(varname,lookupTable,origin,width,height,countMajor,countMinor){"use strict";function createLabels(varname,positions,range){if(!positions)return void console.log("[error] Create labels requires positions (x,y,z) array");if(positions.length%3!==0)return void console.log("[error] Create labels require positions array contain 3d points");if(!range)return void console.log("[error] Create labels requires Valid range");var i,actor=null,size=vgl.utils.computePowerOfTwo(48),index=0,actors=[],origin=[],pt1=[],pt2=[],delta=positions[6]-positions[0],axisLabelOffset=4;for(origin.length=3,pt1.length=3,pt2.length=3,i=0;2>i;i+=1)index=i*(positions.length-3),origin[0]=positions[index]-delta,origin[1]=positions[index+1]-2*delta,origin[2]=positions[index+2],pt1[0]=positions[index]+delta,pt1[1]=origin[1],pt1[2]=origin[2],pt2[0]=origin[0],pt2[1]=positions[1],pt2[2]=origin[2],actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1[0],pt1[1],pt1[2],pt2[0],pt2[1],pt2[2],!0),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.material().addAttribute(vgl.utils.create2DTexture(range[i].toFixed(2).toString(),12,null)),actors.push(actor);return origin[0]=.5*(positions[0]+positions[positions.length-3]-size),origin[1]=positions[1]+axisLabelOffset,origin[2]=positions[2],pt1[0]=origin[0]+size,pt1[1]=origin[1],pt1[2]=origin[2],pt2[0]=origin[0],pt2[1]=origin[1]+size,pt2[2]=origin[2],actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1[0],pt1[1],pt1[2],pt2[0],pt2[1],pt2[2],!0),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.material().addAttribute(vgl.utils.create2DTexture(varname,24,null)),actors.push(actor),actors}function createTicksAndLabels(varname,lut,originX,originY,originZ,pt1X,pt1Y,pt1Z,pt2X,pt2Y,pt2Z,countMajor,countMinor,heightMajor,heightMinor){heightMinor=heightMinor;var width=pt2X-pt1X,index=null,delta=width/countMajor,positions=[],actors=[];for(index=0;countMajor>=index;index+=1)positions.push(pt1X+delta*index),positions.push(pt1Y),positions.push(pt1Z),positions.push(pt1X+delta*index),positions.push(pt1Y+heightMajor),positions.push(pt1Z);return actors=actors.concat(createLabels(varname,positions,lut.range()))}if(!lookupTable)return console.log("[error] Invalid lookup table"),[];var pt1X=origin[0]+width,pt1Y=origin[1],pt1Z=0,pt2X=origin[0],pt2Y=origin[1]+height,pt2Z=0,actors=[],actor=null,mat=null,group=vgl.groupNode();return actor=vgl.utils.createTexturePlane(origin[0],origin[1],origin[2],pt1X,pt1Y,pt1Z,pt2X,pt2Y,pt2Z,!0),mat=actor.material(),mat.addAttribute(lookupTable),actor.setMaterial(mat),group.addChild(actor),actor.material().setBinNumber(vgl.material.RenderBin.Overlay),actor.setReferenceFrame(vgl.boundingObject.ReferenceFrame.Absolute),actors.push(actor),actors=actors.concat(createTicksAndLabels(varname,lookupTable,origin[0],origin[1],origin[1],pt2X,pt1Y,pt1Z,pt1X,pt1Y,pt1Z,countMajor,countMinor,5,3))},vgl.utils.create2DTexture=function(textToWrite,textSize,color,font,alignment,baseline,bold){"use strict";var canvas=document.getElementById("textRendering"),ctx=null,texture=vgl.texture();return font=font||"sans-serif",alignment=alignment||"center",baseline=baseline||"bottom","undefined"==typeof bold&&(bold=!0),canvas||(canvas=document.createElement("canvas")),ctx=canvas.getContext("2d"),canvas.setAttribute("id","textRendering"),canvas.style.display="none",canvas.height=vgl.utils.computePowerOfTwo(8*textSize),canvas.width=canvas.height,ctx.fillStyle="rgba(0, 0, 0, 0)",ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height),ctx.fillStyle="rgba(200, 85, 10, 1.0)",ctx.textAlign=alignment,ctx.textBaseline=baseline,ctx.font=4*textSize+"px "+font,bold&&(ctx.font="bold "+ctx.font),ctx.fillText(textToWrite,canvas.width/2,canvas.height/2,canvas.width),texture.setImage(canvas),texture.updateDimensions(),texture},vgl.picker=function(){"use strict";if(!(this instanceof vgl.picker))return new vgl.picker;vgl.object.call(this);var m_actors=[];return this.getActors=function(){return m_actors},this.pick=function(selectionX,selectionY,renderer){if(void 0===selectionX)return 0;if(void 0===selectionY)return 0;if(void 0===renderer)return 0;m_actors=[];var actors,count,i,bb,tmin,tmax,tymin,tymax,tzmin,tzmax,actor,camera=renderer.camera(),width=renderer.width(),height=renderer.height(),fpoint=camera.focalPoint(),focusWorldPt=vec4.fromValues(fpoint[0],fpoint[1],fpoint[2],1),focusDisplayPt=renderer.worldToDisplay(focusWorldPt,camera.viewMatrix(),camera.projectionMatrix(),width,height),displayPt=vec4.fromValues(selectionX,selectionY,focusDisplayPt[2],1),worldPt=renderer.displayToWorld(displayPt,camera.viewMatrix(),camera.projectionMatrix(),width,height),cameraPos=camera.position(),ray=[];for(i=0;3>i;i+=1)ray[i]=worldPt[i]-cameraPos[i];for(actors=renderer.sceneRoot().children(),count=0,i=0;i=0?(tmin=(bb[0]-cameraPos[0])/ray[0],tmax=(bb[1]-cameraPos[0])/ray[0]):(tmin=(bb[1]-cameraPos[0])/ray[0],tmax=(bb[0]-cameraPos[0])/ray[0]),ray[1]>=0?(tymin=(bb[2]-cameraPos[1])/ray[1],tymax=(bb[3]-cameraPos[1])/ray[1]):(tymin=(bb[3]-cameraPos[1])/ray[1],tymax=(bb[2]-cameraPos[1])/ray[1]),tmin>tymax||tymin>tmax)continue;if(tymin>tmin&&(tmin=tymin),tmax>tymax&&(tmax=tymax),ray[2]>=0?(tzmin=(bb[4]-cameraPos[2])/ray[2],tzmax=(bb[5]-cameraPos[2])/ray[2]):(tzmin=(bb[5]-cameraPos[2])/ray[2],tzmax=(bb[4]-cameraPos[2])/ray[2]),tmin>tzmax||tzmin>tmax)continue;tzmin>tmin&&(tmin=tzmin),tmax>tzmax&&(tmax=tzmax),m_actors[count]=actor,count+=1}return count},this},inherit(vgl.picker,vgl.object),vgl.shapefileReader=function(){"use strict";if(!(this instanceof vgl.shapefileReader))return new vgl.shapefileReader;var m_that=this,SHP_NULL=0,SHP_POINT=1,SHP_POLYGON=5,SHP_POLYLINE=3;return this.int8=function(data,offset){return data.charCodeAt(offset)},this.bint32=function(data,offset){return((255&data.charCodeAt(offset))<<24)+((255&data.charCodeAt(offset+1))<<16)+((255&data.charCodeAt(offset+2))<<8)+(255&data.charCodeAt(offset+3))},this.lint32=function(data,offset){return((255&data.charCodeAt(offset+3))<<24)+((255&data.charCodeAt(offset+2))<<16)+((255&data.charCodeAt(offset+1))<<8)+(255&data.charCodeAt(offset))},this.bint16=function(data,offset){return((255&data.charCodeAt(offset))<<8)+(255&data.charCodeAt(offset+1))},this.lint16=function(data,offset){return((255&data.charCodeAt(offset+1))<<8)+(255&data.charCodeAt(offset))},this.ldbl64=function(data,offset){var b0=255&data.charCodeAt(offset),b1=255&data.charCodeAt(offset+1),b2=255&data.charCodeAt(offset+2),b3=255&data.charCodeAt(offset+3),b4=255&data.charCodeAt(offset+4),b5=255&data.charCodeAt(offset+5),b6=255&data.charCodeAt(offset+6),b7=255&data.charCodeAt(offset+7),sign=1-2*(b7>>7),exp=((127&b7)<<4)+((240&b6)>>4)-1023,frac=(15&b6)*Math.pow(2,48)+b5*Math.pow(2,40)+b4*Math.pow(2,32)+b3*Math.pow(2,24)+b2*Math.pow(2,16)+b1*Math.pow(2,8)+b0;return sign*(1+frac*Math.pow(2,-52))*Math.pow(2,exp)},this.lfloat32=function(data,offset){var b0=255&data.charCodeAt(offset),b1=255&data.charCodeAt(offset+1),b2=255&data.charCodeAt(offset+2),b3=255&data.charCodeAt(offset+3),sign=1-2*(b3>>7),exp=((127&b3)<<1)+((254&b2)>>7)-127,frac=(127&b2)*Math.pow(2,16)+b1*Math.pow(2,8)+b0;return sign*(1+frac*Math.pow(2,-23))*Math.pow(2,exp)},this.str=function(data,offset,length){for(var chars=[],index=offset;offset+length>index;){var c=data[index];if(0===c.charCodeAt(0))break;chars.push(c),index+=1}return chars.join("")},this.readHeader=function(data){var code=this.bint32(data,0),length=this.bint32(data,24),version=this.lint32(data,28),shapetype=this.lint32(data,32);return{code:code,length:length,version:version,shapetype:shapetype}},this.loadShx=function(data){for(var indices=[],appendIndex=function(offset){return indices.push(2*m_that.bint32(data,offset)),offset+8},offset=100;offsetheader_offset;)headers.push(readHeader(header_offset)),header_offset+=HEADER_LENGTH;for(var records=[],record_offset=header_size;header_size+num_entries*record_size>record_offset;){var declare=m_that.str(data,record_offset,1);if("*"===declare)record_offset+=record_size;else{record_offset+=1;for(var record={},i=0;i=start;i-=1){var x=m_that.ldbl64(data,offset+16*i),y=m_that.ldbl64(data,offset+16*i+8);ring.push([x,y])}return ring},readRecord=function(offset){var num_parts,num_points,parts_start,points_start,i,start,end,ring,rings,record_offset=offset+8,geom_type=m_that.lint32(data,record_offset);if(geom_type===SHP_NULL)console.log("NULL Shape");else if(geom_type===SHP_POINT){var x=m_that.ldbl64(data,record_offset+4),y=m_that.ldbl64(data,record_offset+12);features.push({type:"Point",attr:{},geom:[[x,y]]})}else if(geom_type===SHP_POLYGON){for(num_parts=m_that.lint32(data,record_offset+36),num_points=m_that.lint32(data,record_offset+40),parts_start=offset+52,points_start=offset+52+4*num_parts,rings=[],i=0;num_parts>i;i+=1)start=m_that.lint32(data,parts_start+4*i),end=num_parts>i+1?m_that.lint32(data,parts_start+4*(i+1)):num_points,ring=readRing(points_start,start,end),rings.push(ring);features.push({type:"Polygon",attr:{},geom:[rings]})}else{if(geom_type!==SHP_POLYLINE)throw"Not Implemented: "+geom_type;for(num_parts=m_that.lint32(data,record_offset+36),num_points=m_that.lint32(data,record_offset+40),parts_start=offset+52,points_start=offset+52+4*num_parts,rings=[],i=0;num_parts>i;i+=1)start=m_that.lint32(data,parts_start+4*i),end=num_parts>i+1?m_that.lint32(data,parts_start+4*(i+1)):num_points,ring=readRing(points_start,start,end),rings.push(ring);features.push({type:"Polyline",attr:{},geom:[rings]})}},attr=this.loadDBF(dbf_data);for(i=0;i=m_base64Str.length)return END_OF_INPUT;if(nextCharacter=m_base64Str.charAt(m_base64Count),m_base64Count+=1,m_reverseBase64Chars[nextCharacter])return m_reverseBase64Chars[nextCharacter];if("A"===nextCharacter)return 0}return END_OF_INPUT},this.decode64=function(str){var result="",inBuffer=new Array(4),done=!1;for(m_base64Str=str,m_base64Count=0;!done&&(inBuffer[0]=this.readReverseBase64())!==END_OF_INPUT&&(inBuffer[1]=this.readReverseBase64())!==END_OF_INPUT;)inBuffer[2]=this.readReverseBase64(),inBuffer[3]=this.readReverseBase64(),result+=this.ntos(inBuffer[0]<<2&255|inBuffer[1]>>4),inBuffer[2]!==END_OF_INPUT?(result+=this.ntos(inBuffer[1]<<4&255|inBuffer[2]>>2),inBuffer[3]!==END_OF_INPUT?result+=this.ntos(inBuffer[2]<<6&255|inBuffer[3]):done=!0):done=!0;return result},this.readNumber=function(ss){var v=ss[m_pos++]+(ss[m_pos++]<<8)+(ss[m_pos++]<<16)+(ss[m_pos++]<<24);return v},this.readF3Array=function(numberOfPoints,ss){var i,size=4*numberOfPoints*3,test=new Int8Array(size),points=null;for(i=0;size>i;i+=1)test[i]=ss[m_pos],m_pos+=1;return points=new Float32Array(test.buffer)},this.readColorArray=function(numberOfPoints,ss,vglcolors){var i,idx=0,tmp=new Array(3*numberOfPoints);for(i=0;numberOfPoints>i;i+=1)tmp[idx++]=ss[m_pos++]/255,tmp[idx++]=ss[m_pos++]/255,tmp[idx++]=ss[m_pos++]/255,m_pos++;vglcolors.insert(tmp)},this.parseObject=function(vtkObject){var size,actor,colorMapData,shaderProg,opacityUniform,lookupTable,colorTable,windowSize,width,height,position,geom=new vgl.geometryData,mapper=vgl.mapper(),ss=[],type=null,data=null,matrix=null,material=null;for(data=atob(vtkObject.data),i=0;ii;i+=1)p[idx++]=points[3*i],p[idx++]=points[3*i+1],p[idx++]=points[3*i+2];for(vglpoints.insert(p),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),vgllines=new vgl.lines,geom.addPrimitive(vgllines),numberOfIndex=this.readNumber(ss),temp=new Int8Array(2*numberOfIndex),i=0;2*numberOfIndex>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;for(index=new Uint16Array(temp.buffer),vgllines.setIndices(index),vgllines.setPrimitiveType(vgl.GL.LINES),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),matrix},this.parseMeshData=function(geom,ss){var numberOfIndex,numberOfPoints,points,temp,index,size,m,i,tcoord,vglpoints=null,vglcolors=null,normals=null,matrix=mat4.create(),vgltriangles=null,pn=null,idx=0;for(numberOfPoints=this.readNumber(ss),pn=new Array(6*numberOfPoints),vglpoints=new vgl.sourceDataP3N3f,points=this.readF3Array(numberOfPoints,ss),normals=this.readF3Array(numberOfPoints,ss),i=0;numberOfPoints>i;i+=1)pn[idx++]=points[3*i],pn[idx++]=points[3*i+1],pn[idx++]=points[3*i+2],pn[idx++]=normals[3*i],pn[idx++]=normals[3*i+1],pn[idx++]=normals[3*i+2];for(vglpoints.insert(pn),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),temp=[],vgltriangles=new vgl.triangles,numberOfIndex=this.readNumber(ss),temp=new Int8Array(2*numberOfIndex),i=0;2*numberOfIndex>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;for(index=new Uint16Array(temp.buffer),vgltriangles.setIndices(index),geom.addPrimitive(vgltriangles),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),tcoord=null,matrix},this.parsePointData=function(geom,ss){var numberOfPoints,points,indices,temp,size,m,matrix=mat4.create(),vglpoints=null,vglcolors=null,vglVertexes=null,p=null,idx=0;for(numberOfPoints=this.readNumber(ss),p=new Array(3*numberOfPoints),vglpoints=new vgl.sourceDataP3fv,points=this.readF3Array(numberOfPoints,ss),indices=new Uint16Array(numberOfPoints),i=0;numberOfPoints>i;i+=1)indices[i]=i,p[idx++]=points[3*i],p[idx++]=points[3*i+1],p[idx++]=points[3*i+2];for(vglpoints.insert(p),geom.addSource(vglpoints),vglcolors=new vgl.sourceDataC3fv,this.readColorArray(numberOfPoints,ss,vglcolors),geom.addSource(vglcolors),vglVertexes=new vgl.points,vglVertexes.setIndices(indices),geom.addPrimitive(vglVertexes),size=64,temp=new Int8Array(size),i=0;size>i;i+=1)temp[i]=ss[m_pos],m_pos+=1;return m=new Float32Array(temp.buffer),mat4.copy(matrix,m),matrix},this.parseColorMapData=function(geom,ss,numColors){var tmpArray,size,xrgb,i,c,obj={};for(obj.numOfColors=numColors,size=8,tmpArray=new Int8Array(size),i=0;size>i;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;for(obj.position=new Float32Array(tmpArray.buffer),size=8,tmpArray=new Int8Array(size),i=0;size>i;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;for(obj.size=new Float32Array(tmpArray.buffer),obj.colors=[],c=0;ci;i+=1)tmpArray[i]=ss[m_pos],m_pos+=1;xrgb=[new Float32Array(tmpArray.buffer)[0],ss[m_pos++],ss[m_pos++],ss[m_pos++]],obj.colors[c]=xrgb}for(obj.orientation=ss[m_pos++],obj.numOfLabels=ss[m_pos++],obj.title="";m_pos=0;layer-=1)renderer=this.getRenderer(layer),this.parseSceneMetadata(renderer,layer);return m_viewer},this.createViewer=function(node){var interactorStyle;return null===m_viewer&&(m_node=node,m_viewer=vgl.viewer(node),m_viewer.init(),m_viewer.renderWindow().removeRenderer(m_viewer.renderWindow().activeRenderer()),m_viewer.renderWindow().addRenderer(new vgl.depthPeelRenderer),m_vtkRenderedList[0]=m_viewer.renderWindow().activeRenderer(),m_viewer.renderWindow().resize(node.width,node.height),interactorStyle=vgl.pvwInteractorStyle(),m_viewer.setInteractorStyle(interactorStyle)),m_viewer},this.deleteViewer=function(){m_vtkRenderedList={},m_viewer=null},this.updateCanvas=function(node){return m_node=node,m_viewer.renderWindow().resize(node.width,node.height),m_viewer},this.numObjects=function(){return m_vtkObjectCount},this.getRenderer=function(layer){var renderer;return renderer=m_vtkRenderedList[layer],(null===renderer||"undefined"==typeof renderer)&&(renderer=new vgl.renderer,renderer.setResetScene(!1),renderer.setResetClippingRange(!1),m_viewer.renderWindow().addRenderer(renderer),0!==layer&&renderer.camera().setClearMask(vgl.GL.DepthBufferBit),m_vtkRenderedList[layer]=renderer),renderer},this.setVtkScene=function(scene){m_vtkScene=scene},this},vgl.DataBuffers=function(initialSize){"use strict";if(!(this instanceof vgl.DataBuffers))return new vgl.DataBuffers(initialSize);var size,data={};size=initialSize||0===initialSize?initialSize:256;var current=0,copyArray=function(dst,src,start,count){dst||console.log("ack"),start||(start=0),count||(count=src.length);for(var i=0;count>i;i+=1)dst[start+i]=src[i]},resize=function(min_expand){var new_size=size;for(min_expand>2*new_size&&(new_size=min_expand);min_expand>new_size;)new_size*=2;size=new_size;for(var name in data)if(data.hasOwnProperty(name)){var newArray=new Float32Array(new_size*data[name].len),oldArray=data[name].array;copyArray(newArray,oldArray),data[name].array=newArray,data[name].dirty=!0}};this.create=function(name,len){if(!len)throw"Length of buffer must be a positive integer";var array=new Float32Array(size*len);return data[name]={array:array,len:len,dirty:!1},data[name].array},this.alloc=function(num){current+num>=size&&resize(current+num);var start=current;return current+=num,start},this.get=function(name){return data[name].array},this.write=function(name,array,start,count){copyArray(data[name].array,array,start*data[name].len,count*data[name].len),data[name].dirty=!0},this.repeat=function(name,elem,start,count){for(var i=0;count>i;i+=1)copyArray(data[name].array,elem,(start+i)*data[name].len,data[name].len);data[name].dirty=!0},this.count=function(){return current},this.data=function(name){return data[name].array}},function(){"use strict";function setNumeric(){var i;for(i=0;in?!1:(outer.forEach(function(vert,i){var j=(n+i-1)%n,intersect=outer[i].y>point.y!=outer[j].y>point.y&&point.x<(outer[j].x-outer[i].x)*(point.y-outer[i].y)/(outer[j].y-outer[i].y)+outer[i].x;intersect&&(inside=!inside)}),(inner||[]).forEach(function(hole){inside=inside&&!geo.util.pointInPolygon(point,hole)}),inside)},isFunction:function(f){return"function"==typeof f},ensureFunction:function(f){return geo.util.isFunction(f)?f:function(){return f}},randomString:function(n){var s,i,r;for(n=n||8,s="",i=0;n>i;i+=1)r=Math.floor(Math.random()*chars.length),s+=chars.substring(r,r+1);return s},convertColor:function(color){return void 0!==color.r&&void 0!==color.g&&void 0!==color.b?color:("string"==typeof color&&(geo.util.cssColors.hasOwnProperty(color)?color=geo.util.cssColors[color]:"#"===color.charAt(0)&&(color=parseInt(color.slice(1),16))),isFinite(color)&&(color={r:((16711680&color)>>16)/255,g:((65280&color)>>8)/255,b:(255&color)/255}),color)},normalizeCoordinates:function(p){return p=p||{},Array.isArray(p)?{x:p[0],y:p[1],z:p[2]||0}:{x:setNumeric(p.x,p.longitude,p.lng,p.lon,0),y:setNumeric(p.y,p.latitude,p.lat,0),z:setNumeric(p.z,p.elevation,p.elev,p.height,0)}},radiusEarth:6378137,lincomb:function(ca,a,cb,b){return a.x=ca*(a.x||0)+cb*(b.x||0),a.y=ca*(a.y||0)+cb*(b.y||0),a.z=ca*(a.x||0)+cb*(b.x||0),a},scale:function(a,b,pow){return a.x=(a.x||0)*Math.pow(b.x||1,pow),a.y=(a.y||0)*Math.pow(b.y||1,pow),a.z=(a.z||0)*Math.pow(b.z||1,pow),a}},geo.util.cssColors={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,
+mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074}}(),function(){"use strict";function vect(x,y){return new Vector2D(x,y)}var RangeNode=function(elem,start,end,current){this.data=elem[current],this.left=null,this.right=null,start!==current&&(this.left=new RangeNode(elem,start,current-1,parseInt((start+(current-1))/2,10))),end!==current&&(this.right=new RangeNode(elem,current+1,end,parseInt((end+(current+1))/2,10))),this.elem=elem,this.start=start,this.end=end,this.subtree=null,this.search=rangeNodeSearch},rangeNodeSearch=function(result,box){var m_this=this,xrange=function(b){return b.x_in(m_this.elem[m_this.start])&&b.x_in(m_this.elem[m_this.end])},yrange=function(b,start,end){return b.y_in(m_this.subtree[start])&&b.y_in(m_this.subtree[end])},subquery=function(result,box,start,end,current){if(yrange(box,start,end))for(var i=start;end>=i;i++)result.push(m_this.subtree[i]);else box.y_in(m_this.subtree[current])&&result.push(m_this.subtree[current]),box.y_left(m_this.subtree[current])?current!==end&&subquery(result,box,current+1,end,parseInt((end+(current+1))/2,10)):box.x_right(m_this.subtree[current])?current!==start&&subquery(result,box,start,current-1,parseInt((start+(current-1))/2,10)):(current!==end&&subquery(result,box,current+1,end,parseInt((end+(current+1))/2,10)),current!==start&&subquery(result,box,start,current-1,parseInt((start+(current-1))/2,10)))};return xrange(box)?(this.subtree||(this.subtree=this.elem.slice(this.start,this.end+1),this.subtree.sort(function(a,b){return a.y-b.y})),void subquery(result,box,0,this.subtree.length-1,parseInt((this.subtree.length-1)/2,10))):(box.contains(this.data)&&result.push(this.data),void(box.x_left(this.data)?this.right&&this.right.search(result,box):box.x_right(this.data)?this.left&&this.left.search(result,box):(this.left&&this.left.search(result,box),this.right&&this.right.search(result,box))))},RangeTree=function(elem){elem.sort(function(a,b){return a.x-b.x}),elem.length>0?this.root=new RangeNode(elem,0,elem.length-1,parseInt((elem.length-1)/2,10)):this.root=null,this.search=function(_box){if(!this.root)return[];var box=_box.clone(),result=[];return this.root.search(result,box),result}},Box=function(v1,v2){this.min=v1.clone(),this.max=v2.clone(),this.contains=function(p){return v1.x<=p.x&&v2.x>=p.x&&v1.y<=p.y&&v2.y>=p.y},this.x_in=function(p){return v1.x<=p.x&&v2.x>=p.x},this.x_left=function(p){return v1.x>=p.x},this.x_right=function(p){return v2.x<=p.x},this.y_in=function(p){return v1.y<=p.y&&v2.y>=p.y},this.y_left=function(p){return v1.y>=p.y},this.y_right=function(p){return v2.y<=p.y},this.area=function(){return(this.max.x-this.min.x)*(this.max.y-this.min.y)},this.height=function(){return this.max.y-this.min.y},this.width=function(){return this.max.x-this.min.x},this.vertex=function(index){switch(index){case 0:return this.min.clone();case 1:return new vect(this.max.x,this.min.y);case 2:return this.max.clone();case 3:return new vect(this.min.x,this.max.y);default:throw"Index out of bounds: "+index}},this.intersects=function(box){for(var i=0;4>i;i++)for(var j=0;4>j;j++)if(vect.intersects(this.vertex(i),this.vertex((i+1)%4),box.vertex(j),box.vertex((j+1)%4)))return!0;return this.contains(box.min)&&this.contains(box.max)&&this.contains(new vect(box.min.x,box.max.y))&&this.contains(new vect(box.max.x,box.min.y))?!0:box.contains(this.min)&&box.contains(this.max)&&box.contains(new vect(this.min.x,this.max.y))&&box.contains(new vect(this.max.x,this.min.y))?!0:!1},this.union=function(b){this.min.x=Math.min(this.min.x,b.min.x),this.min.y=Math.min(this.min.y,b.min.y),this.max.x=Math.max(this.max.x,b.max.x),this.max.y=Math.max(this.max.y,b.max.y)},this.centroid=function(){return new vect((this.max.x+this.min.x)/2,(this.max.y+this.min.y)/2)},this.clone=function(){return new Box(v1,v2)}},Vector2D=function(x,y){this.x=x,this.y=y,this.add=function(v){return this.x+=v.x,this.y+=v.y,this},this.sub=function(v){return this.x-=v.x,this.y-=v.y,this},this.scale=function(s){return this.x*=s,this.y*=s,this},this.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},this.normalize=function(){var scale=this.length();return 0===scale?this:(this.x/=scale,this.y/=scale,this)},this.div=function(v){return this.x/=v.x,this.y/=v.y,this},this.floor=function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},this.zero=function(tol){return tol=tol||0,this.length()<=tol},this.dot=function(v){return this.x*v.x+this.y*v.y},this.cross=function(v){return this.x*v.y-this.y*v.x},this.rotate=function(omega){var cos=Math.cos(omega),sin=Math.sin(omega);return xp=cos*this.x-sin*this.y,yp=sin*this.x+cos*this.y,this.x=xp,this.y=yp,this},this.clone=function(){return new Vector2D(this.x,this.y)},this.array=function(){return[this.x,this.y]}};vect.scale=function(v,s){return v.clone().scale(s)},vect.add=function(v1,v2){return v1.clone().add(v2)},vect.sub=function(v1,v2){return v1.clone().sub(v2)},vect.dist=function(v1,v2){return v1.clone().sub(v2).length()},vect.dir=function(v1,v2){return v1.clone().sub(v2).normalize()},vect.dot=function(v1,v2){return v1.x*v2.x+v1.y*v2.y},vect.cross=function(v1,v2){return v1.x*v2.y-v1.y*v2.x},vect.left=function(a,b,c,tol){tol||(tol=0);var v1=vect.sub(b,a),v2=vect.sub(c,a);return vect.cross(v1,v2)>=-tol},vect.intersects=function(a,b,c,d,tol){return tol||(tol=0),vect.left(a,b,c,tol)!=vect.left(a,b,d,tol)&&vect.left(c,d,b,tol)!=vect.left(c,d,a,tol)},vect.intersect2dt=function(a,b,c,d){var denom=a.x*(d.y-c.y)+b.x*(c.y-d.y)+d.x*(b.y-a.y)+c.x*(a.y-b.y);if(0===denom)return 1/0;var num_t=(a.x*(d.y-c.y)+c.x*(a.y-d.y)+d.x*(c.y-a.y),-(a.x*(c.y-b.y)+b.x*(a.y-c.y)+c.x*(b.y-a.y))),t=num_t/denom;return t},vect.intersect2dpos=function(a,b,c,d){var denom=a.x*(d.y-c.y)+b.x*(c.y-d.y)+d.x*(b.y-a.y)+c.x*(a.y-b.y);if(0===denom)return 1/0;var num_s=a.x*(d.y-c.y)+c.x*(a.y-d.y)+d.x*(c.y-a.y),s=num_s/denom,dir=vect.sub(b,a);return dir.scale(s),vect.add(a,dir)},vect.rotate=function(v,omega){var cos=Math.cos(omega),sin=Math.sin(omega);xp=cos*v.x-sin*v.y,yp=sin*v.x+cos*v.y;var c=new vect(xp,yp);return c},vect.normalize=function(v){return v.clone().normalize()},geo.util.RangeTree=RangeTree,geo.util.Box=Box,geo.util.vect=vect}(),function(){"use strict";var L={};L.Util={stamp:function(obj){return obj._leaflet_id=obj._leaflet_id||++L.Util.lastId,obj._leaflet_id},lastId:0},geo.util.DistanceGrid=function(cellSize){this._cellSize=cellSize,this._sqCellSize=cellSize*cellSize,this._grid={},this._objectPoint={}},geo.util.DistanceGrid.prototype={addObject:function(obj,point){var x=this._getCoord(point.x),y=this._getCoord(point.y),grid=this._grid,row=grid[y]=grid[y]||{},cell=row[x]=row[x]||[],stamp=L.Util.stamp(obj);point.obj=obj,this._objectPoint[stamp]=point,cell.push(obj)},updateObject:function(obj,point){this.removeObject(obj),this.addObject(obj,point)},removeObject:function(obj,point){var i,len,x=this._getCoord(point.x),y=this._getCoord(point.y),grid=this._grid,row=grid[y]=grid[y]||{},cell=row[x]=row[x]||[];for(delete this._objectPoint[L.Util.stamp(obj)],i=0,len=cell.length;len>i;i++)if(cell[i]===obj)return cell.splice(i,1),1===len&&delete row[x],!0},eachObject:function(fn,context){var i,j,k,len,row,cell,removed,grid=this._grid;for(i in grid){row=grid[i];for(j in row)for(cell=row[j],k=0,len=cell.length;len>k;k++)removed=fn.call(context,cell[k]),removed&&(k--,len--)}},getNearObject:function(point){var i,j,k,row,cell,len,obj,dist,x=this._getCoord(point.x),y=this._getCoord(point.y),objectPoint=this._objectPoint,closestDistSq=this._sqCellSize,closest=null;for(i=y-1;y+1>=i;i++)if(row=this._grid[i])for(j=x-1;x+1>=j;j++)if(cell=row[j])for(k=0,len=cell.length;len>k;k++)obj=cell[k],dist=this._sqDist(objectPoint[L.Util.stamp(obj)],point),closestDistSq>dist&&(closestDistSq=dist,closest=obj);return closest},contents:function(){return $.map(this._objectPoint,function(val){return val})},_getCoord:function(x){return Math.floor(x/this._cellSize)},_sqDist:function(p,p2){var dx=p2.x-p.x,dy=p2.y-p.y;return dx*dx+dy*dy}}}(),function(){"use strict";function ClusterTree(group,zoom,children){this._group=group,this._zoom=zoom,this._points=[],this._clusters=[],this._count=0,this._parent=null,this._coord=null;var that=this;(children||[]).forEach(function(c){that._add(c)})}function C(opts,width,height){this._opts=$.extend({maxZoom:18,radius:.05},opts),this._opts.width=this._opts.width||width||256,this._opts.height=this._opts.height||height||256,this._clusters={},this._points={};var zoom,scl;for(zoom=this._opts.maxZoom;zoom>=0;zoom-=1)scl=this._scaleAtLevel(zoom,this._opts.width,this._opts.height),this._clusters[zoom]=new geo.util.DistanceGrid(scl),this._points[zoom]=new geo.util.DistanceGrid(scl);this._topClusterLevel=new ClusterTree(this,-1)}ClusterTree.prototype._add=function(pt){var inc=1;pt instanceof ClusterTree?(this._clusters.push(pt),inc=pt._count):this._points.push(pt),pt._parent=this,this._increment(inc)},ClusterTree.prototype._increment=function(inc){this._coord=null,this._count+=inc,this._parent&&this._parent._increment(inc)},ClusterTree.prototype.count=function(){return this._count},ClusterTree.prototype.each=function(func){var i;for(i=0;i=0;zoom-=1){if(closest=this._clusters[zoom].getNearObject(point))return void closest._add(point);if(closest=this._points[zoom].getNearObject(point)){if(parent=closest._parent)for(z=parent._points.length-1;z>=0;z-=1)if(parent._points[z]===closest){parent._points.splice(z,1),parent._increment(-1);break}for(parent||$.noop(),newCluster=new ClusterTree(this,zoom,[closest,point]),this._clusters[zoom].addObject(newCluster,newCluster.coords()),lastParent=newCluster,z=zoom-1;z>parent._zoom;z-=1)lastParent=new ClusterTree(this,z,[lastParent]),this._clusters[z].addObject(lastParent,lastParent.coords());for(parent._add(lastParent),z=zoom;z>=0&&this._points[z].removeObject(closest,closest);z-=1);return}this._points[zoom].addObject(point,point)}this._topClusterLevel._add(point)},C.prototype.points=function(zoom){return zoom=Math.min(Math.max(Math.floor(zoom),0),this._opts.maxZoom-1),this._points[Math.floor(zoom)].contents()},C.prototype.clusters=function(zoom){return zoom=Math.min(Math.max(Math.floor(zoom),0),this._opts.maxZoom-1),this._clusters[Math.floor(zoom)].contents()},geo.util.ClusterGroup=C}(),function(){"use strict";geo.util.scale={d3:d3.scale}}(),geo.object=function(){"use strict";if(!(this instanceof geo.object))return new geo.object;var m_this=this,m_eventHandlers={},m_idleHandlers=[],m_promiseCount=0;return this.onIdle=function(handler){return m_promiseCount?m_idleHandlers.push(handler):handler(),m_this},this.addPromise=function(promise){function onDone(){m_promiseCount-=1,m_promiseCount||m_idleHandlers.splice(0,m_idleHandlers.length).forEach(function(handler){handler()})}return m_promiseCount+=1,promise.then(onDone,onDone),m_this},this.geoOn=function(event,handler){return Array.isArray(event)?(event.forEach(function(e){m_this.geoOn(e,handler)}),m_this):(m_eventHandlers.hasOwnProperty(event)||(m_eventHandlers[event]=[]),m_eventHandlers[event].push(handler),m_this)},this.geoTrigger=function(event,args){return Array.isArray(event)?(event.forEach(function(e){m_this.geoTrigger(e,args)}),m_this):(args=args||{},args.event=event,m_eventHandlers.hasOwnProperty(event)&&m_eventHandlers[event].forEach(function(handler){handler.call(m_this,args)}),m_this)},this.geoOff=function(event,arg){if(void 0===event&&(m_eventHandlers={},m_idleHandlers=[],m_promiseCount=0),Array.isArray(event))return event.forEach(function(e){m_this.geoOff(e,arg)}),m_this;if(arg){if(Array.isArray(arg))return arg.forEach(function(handler){m_this.geoOff(event,handler)}),m_this}else m_eventHandlers[event]=[];return m_eventHandlers.hasOwnProperty(event)&&(m_eventHandlers[event]=m_eventHandlers[event].filter(function(f){return f!==arg})),m_this},this._exit=function(){m_this.geoOff()},vgl.object.call(this),this},inherit(geo.object,vgl.object),geo.sceneObject=function(arg){"use strict";if(!(this instanceof geo.sceneObject))return new geo.sceneObject;geo.object.call(this,arg);var m_this=this,m_parent=null,m_children=[],s_exit=this._exit,s_trigger=this.geoTrigger,s_addPromise=this.addPromise,s_onIdle=this.onIdle;return this.addPromise=function(promise){m_parent?m_parent.addPromise(promise):s_addPromise(promise)},this.onIdle=function(handler){m_parent?m_parent.onIdle(handler):s_onIdle(handler)},this.parent=function(arg){return void 0===arg?m_parent:(m_parent=arg,m_this)},this.addChild=function(child){return Array.isArray(child)?(child.forEach(m_this.addChild),m_this):(child.parent(m_this),m_children.push(child),m_this)},this.removeChild=function(child){return Array.isArray(child)?(child.forEach(m_this.removeChild),m_this):(m_children=m_children.filter(function(c){return c!==child}),m_this)},this.children=function(){return m_children.slice()},this.draw=function(arg){return m_this.children().forEach(function(child){child.draw(arg)}),m_this},this.geoTrigger=function(event,args,childrenOnly){var geoArgs;return args=args||{},geoArgs=args.geo||{},args.geo=geoArgs,geoArgs.stopPropagation?m_this:!childrenOnly&&m_parent&&geoArgs._triggeredBy!==m_parent?(geoArgs._triggeredBy=m_this,m_parent.geoTrigger(event,args),m_this):(s_trigger.call(m_this,event,args),geoArgs.stopPropagation?m_this:(m_children.forEach(function(child){geoArgs._triggeredBy=m_this,child.geoTrigger(event,args)}),m_this))},this._exit=function(){m_this.children=[],delete m_this.parent,s_exit()},this},inherit(geo.sceneObject,geo.object),geo.timestamp=function(){"use strict";return this instanceof geo.timestamp?void vgl.timestamp.call(this):new geo.timestamp},inherit(geo.timestamp,vgl.timestamp),geo.transform=function(options){"use strict";function generate_proj4(){m_proj=new proj4(m_this.source(),m_this.target())}if(!(this instanceof geo.transform))return new geo.transform(options);var m_proj,m_source,m_target,m_this=this;return this.source=function(arg){return void 0===arg?m_source||"EPSG:4326":(m_source=arg,generate_proj4(),m_this)},this.target=function(arg){return void 0===arg?m_target||"EPSG:3857":(m_target=arg,generate_proj4(),m_this)},this._forward=function(point){var pt=m_proj.forward(point);return pt.z=point.z||0,pt},this._inverse=function(point){var pt=m_proj.inverse(point);return pt.z=point.z||0,pt},this.forward=function(point){return Array.isArray(point)?point.map(m_this._forward):m_this._forward(point)},this.inverse=function(point){return Array.isArray(point)?point.map(m_this._inverse):m_this._inverse(point)},options=options||{},this.source(options.source),this.target(options.target),geo.object.call(this),this},geo.transform.transformCoordinates=function(srcPrj,tgtPrj,coordinates,numberOfComponents){"use strict";function handleArrayCoordinates(){if(coordinates[0]instanceof Array)if(2===coordinates[0].length)xAcc=function(index){return coordinates[index][0]},yAcc=function(index){return coordinates[index][1]},writer=function(index,x,y){output[index]=[x,y]};else{if(3!==coordinates[0].length)throw"Invalid coordinates. Requires two or three components per array";xAcc=function(index){return coordinates[index][0]},yAcc=function(index){return coordinates[index][1]},zAcc=function(index){return coordinates[index][2]},writer=function(index,x,y,z){output[index]=[x,y,z]}}else if(2===coordinates.length)offset=2,xAcc=function(index){return coordinates[index*offset]},yAcc=function(index){return coordinates[index*offset+1]},writer=function(index,x,y){output[index]=x,output[index+1]=y};else if(3===coordinates.length)offset=3,xAcc=function(index){return coordinates[index*offset]},yAcc=function(index){return coordinates[index*offset+1]},zAcc=function(index){return coordinates[index*offset+2]},writer=function(index,x,y,z){output[index]=x,output[index+1]=y,output[index+2]=z};else{if(!numberOfComponents)throw"Invalid coordinates";if(2!==numberOfComponents&&3!==numberOfComponents)throw"Number of components should be two or three";offset=numberOfComponents,xAcc=function(index){return coordinates[index]},yAcc=function(index){return coordinates[index+1]},2===numberOfComponents?writer=function(index,x,y){output[index]=x,output[index+1]=y}:(zAcc=function(index){return coordinates[index+2]},writer=function(index,x,y,z){output[index]=x,output[index+1]=y,output[index+2]=z})}}function handleObjectCoordinates(){if(coordinates[0]&&"x"in coordinates[0]&&"y"in coordinates[0])xAcc=function(index){return coordinates[index].x},yAcc=function(index){return coordinates[index].y},"z"in coordinates[0]?(zAcc=function(index){return coordinates[index].z},writer=function(index,x,y,z){output[i]={x:x,y:y,z:z}}):writer=function(index,x,y){output[index]={x:x,y:y}};else{if(!(coordinates&&"x"in coordinates&&"y"in coordinates))throw"Invalid coordinates";xAcc=function(){return coordinates.x},yAcc=function(){return coordinates.y},"z"in coordinates?(zAcc=function(){return coordinates.z},writer=function(index,x,y,z){output={x:x,y:y,z:z}}):writer=function(index,x,y){output={x:x,y:y}}}}if(srcPrj===tgtPrj)return coordinates;var i,count,offset,xAcc,yAcc,zAcc,writer,output,projPoint,trans=geo.transform({source:srcPrj,target:tgtPrj});if(zAcc=function(){return 0},coordinates instanceof Array)output=[],output.length=coordinates.length,count=coordinates.length,coordinates[0]instanceof Array||coordinates[0]instanceof Object?(offset=1,coordinates[0]instanceof Array?handleArrayCoordinates():coordinates[0]instanceof Object&&handleObjectCoordinates()):handleArrayCoordinates();else if(coordinates&&coordinates instanceof Object){if(count=1,offset=1,!(coordinates&&"x"in coordinates&&"y"in coordinates))throw"Coordinates are not valid";handleObjectCoordinates()}for(i=0;count>i;i+=offset)projPoint=trans.forward({x:xAcc(i),y:yAcc(i),z:zAcc(i)}),writer(i,projPoint.x,projPoint.y,projPoint.z);return output},geo.transform.affineForward=function(def,coords){"use strict";var i,origin=def.origin,scale=def.scale||{x:1,y:1,z:1};for(i=0;i0&&viewport.height>0))throw new Error("Invalid viewport dimensions");(viewport.width!==this._viewport.width||viewport.height!==this._viewport.height)&&(this._viewport.width&&this._viewport.height&&(this._scale(vec3.fromValues(this._viewport.width/viewport.width,this._viewport.height/viewport.height,1)),this._translate(vec3.fromValues((viewport.width-this._viewport.width)/2,(viewport.height-this._viewport.height)/2,0))),this._viewport={width:viewport.width,height:viewport.height},this._update(),this.geoTrigger(geo.event.camera.viewport,{camera:this,viewport:this.viewport}))}}),this._resetView=function(){return mat4.identity(this._view),this},this._translate=function(offset){mat4.translate(this._view,this._view,offset)},this._scale=function(scale){mat4.scale(this._view,this._view,scale)},this._worldToClip4=function(point){return geo.camera.applyTransform(this._transform,point)},this._clipToWorld4=function(point){return geo.camera.applyTransform(this._inverse,point)},this.applyProjection=function(pt){var w;return"perspective"===this._projection?(w=1/(pt[3]||1),pt[0]=w*pt[0],pt[1]=w*pt[1],pt[2]=w*pt[2],pt[3]=w):pt[3]=1,pt},this.unapplyProjection=function(pt){var w;return"perspective"===this._projection?(w=pt[3]||1,pt[0]=w*pt[0],pt[1]=w*pt[1],pt[2]=w*pt[2],pt[3]=w):pt[3]=1,pt},this.worldToDisplay4=function(point){return point[2]-=2,this._worldToClip4(point),point=this.applyProjection(point),point[0]=this._viewport.width*(1+point[0])/2,point[1]=this._viewport.height*(1-point[1])/2,point[2]=(1+point[2])/2,point},this.displayToWorld4=function(point){return point[0]=2*point[0]/this._viewport.width-1,point[1]=-2*point[1]/this._viewport.height+1,point[2]=2*point[2]-1,point=this.unapplyProjection(point),this._clipToWorld4(point),point[2]+=2,point},this.worldToDisplay=function(point){var z=0,w=1;return point=this.worldToDisplay4([point.x,point.y,z,w]),{x:point[0],y:point[1],z:point[2]}},this.displayToWorld=function(point){var z=1,w=2;return point=this.displayToWorld4([point.x,point.y,z,w]),{x:point[0],y:point[1]}},this._getBounds=function(){var pt,bds={};return pt=this.displayToWorld({x:0,y:this._viewport.height}),bds.left=pt.x,bds.bottom=pt.y,pt=this.displayToWorld({x:this._viewport.width,y:0}),bds.right=pt.x,bds.top=pt.y,bds},this._setBounds=function(bounds){var c_ar,v_ar,w,h,translate=vec3.create(),scale=vec3.create();return bounds.near=bounds.near||0,bounds.far=bounds.far||1,this._resetView(),w=Math.abs(bounds.right-bounds.left),h=Math.abs(bounds.top-bounds.bottom),c_ar=w/h,v_ar=this._viewport.width/this._viewport.height,c_ar>=v_ar?(h=w/v_ar,scale[0]=2/w,scale[1]=2/h):(w=h*v_ar,scale[0]=2/w,scale[1]=2/h),scale[2]=1,this._scale(scale),translate[0]=-(bounds.left+bounds.right)/2,translate[1]=-(bounds.bottom+bounds.top)/2,translate[2]=0,this._translate(translate),this},this.pan=function(offset){this._translate(vec3.fromValues(offset.x,offset.y,offset.z||0)),this._update()},this.zoom=function(zoom){mat4.scale(this._view,this._view,vec3.fromValues(zoom,zoom,zoom)),this._update()},this.css=function(transform){var m;switch((transform||"").toLowerCase()){case"display":case"":m=this.display;break;case"world":m=this.world;break;default:throw new Error("Unknown transform "+transform)}return geo.camera.css(m)},this.ppMatrix=function(mat,prec){function f(i){var d=t[i],s=d.toExponential(prec);return d>=0&&(s=" "+s),s}var t=mat;return prec=prec||2,[[f(0),f(4),f(8),f(12)].join(" "),[f(1),f(5),f(9),f(13)].join(" "),[f(2),f(6),f(10),f(14)].join(" "),[f(3),f(7),f(11),f(15)].join(" ")].join("\n")},this.toString=function(){return this.ppMatrix(this._transform)},this.debug=function(){return["bounds",JSON.stringify(this.bounds),"view:",this.ppMatrix(this._view),"projection:",this.ppMatrix(this._proj),"transform:",this.ppMatrix(this._transform)].join("\n")},this.valueOf=function(){return this._transform},this._resetView(),this.projection=spec.projection||"parallel",spec.viewport&&(this.viewport=spec.viewport),this._update(),this):new geo.camera(spec)},geo.camera.projection={perspective:!0,parallel:!0},geo.camera.bounds={left:-1,right:1,top:1,bottom:-1,far:-2,near:-1},geo.camera.css=function(t){return"matrix3d("+[t[0].toFixed(20),t[1].toFixed(20),t[2].toFixed(20),t[3].toFixed(20),t[4].toFixed(20),t[5].toFixed(20),t[6].toFixed(20),t[7].toFixed(20),t[8].toFixed(20),t[9].toFixed(20),t[10].toFixed(20),t[11].toFixed(20),t[12].toFixed(20),t[13].toFixed(20),t[14].toFixed(20),t[15].toFixed(20)].join(",")+")"},geo.camera.affine=function(pre,scale,post){var mat=mat4.create();return post&&mat4.translate(mat,mat,[post.x||0,post.y||0,post.z||0]),scale&&mat4.scale(mat,mat,[scale.x||1,scale.y||1,scale.z||1]),pre&&mat4.translate(mat,mat,[pre.x||0,pre.y||0,pre.z||0]),mat},geo.camera.applyTransform=function(t,pt){return vec4.transformMat4(pt,pt,t)},geo.camera.combine=function(A,B){return mat4.mul(mat4.create(),A,B)},inherit(geo.camera,geo.object)}(),geo.layer=function(arg){"use strict";if(!(this instanceof geo.layer))return new geo.layer(arg);arg=arg||{},geo.sceneObject.call(this,arg);var m_zIndex,m_this=this,s_exit=this._exit,m_id=void 0===arg.id?geo.layer.newLayerId():arg.id,m_name="",m_map=void 0===arg.map?null:arg.map,m_node=null,m_canvas=null,m_renderer=null,m_initialized=!1,m_rendererName=void 0===arg.renderer?"vgl":arg.renderer,m_dataTime=geo.timestamp(),m_updateTime=geo.timestamp(),m_sticky=void 0===arg.sticky?!0:arg.sticky,m_active=void 0===arg.active?!0:arg.active,m_opacity=void 0===arg.opacity?1:arg.opacity,m_attribution=arg.attribution||null;if(!m_map)throw new Error("Layers must be initialized on a map.");return this.rendererName=function(){return m_rendererName},this.zIndex=function(zIndex){return void 0===zIndex?m_zIndex:(m_zIndex=zIndex,m_node.css("z-index",m_zIndex),m_this)},this.moveUp=function(n){var order,i,tmp,sign,me=null;for(void 0===n&&(n=1),sign=1,0>n&&(sign=-1,n=-n),order=m_this.map().layers().sort(function(a,b){return sign*(a.zIndex()-b.zIndex())}),i=0;i=i-me))break;tmp=m_this.zIndex(),m_this.zIndex(order[i].zIndex()),order[i].zIndex(tmp)}return m_this},this.moveDown=function(n){return void 0===n&&(n=1),m_this.moveUp(-n)},this.moveToTop=function(){return m_this.moveUp(m_this.map().children().length-1)},this.moveToBottom=function(){return m_this.moveDown(m_this.map().children().length-1)},this.sticky=function(){return m_sticky},this.active=function(){return m_active},this.node=function(){return m_node},this.id=function(val){return void 0===val?m_id:(m_id=geo.newLayerId(),m_this.modified(),m_this)},this.name=function(val){return void 0===val?m_name:(m_name=val,m_this.modified(),m_this)},this.map=function(){return m_map},this.renderer=function(){return m_renderer},this.canvas=function(){return m_canvas},this.dataTime=function(){return m_dataTime},this.updateTime=function(){return m_updateTime},this.initialized=function(val){return void 0!==val?(m_initialized=val,m_this):m_initialized},this.toLocal=function(input){return m_this._toLocalMatrix&&geo.camera.applyTransform(m_this._toLocalMatrix,input),input},this.fromLocal=function(input){return m_this._fromLocalMatrix&&geo.camera.applyTransform(m_this._fromLocalMatrix,input),input},this.attribution=function(arg){return void 0!==arg?(m_attribution=arg,m_this.map().updateAttribution(),m_this):m_attribution},this._init=function(){if(m_initialized)return m_this;m_map.node().append(m_node);var options=$.extend({},arg);return delete options.map,null===m_rendererName?(m_renderer=null,m_canvas=m_node):m_canvas?m_renderer=geo.createRenderer(m_rendererName,m_this,m_canvas,options):(m_renderer=geo.createRenderer(m_rendererName,m_this,void 0,options),m_canvas=m_renderer.canvas()),m_this.active()||m_node.css("pointerEvents","none"),m_initialized=!0,m_this.geoOn(geo.event.resize,function(event){m_this._update({event:event})}),m_this.geoOn(geo.event.pan,function(event){m_this._update({event:event})}),m_this.geoOn(geo.event.zoom,function(event){m_this._update({event:event})}),m_this},this._exit=function(){m_this.geoOff(),m_renderer&&m_renderer._exit(),m_node.off(),m_node.remove(),arg={},m_canvas=null,m_renderer=null,s_exit()},this._update=function(){},this.width=function(){return m_this.map().size().width},this.height=function(){return m_this.map().size().height},this.opacity=function(opac){return void 0!==opac?(m_opacity=opac,m_node.css("opacity",m_opacity),m_this):m_opacity},void 0===arg.zIndex&&(arg.zIndex=m_map.children().length),m_zIndex=arg.zIndex,m_node=$(document.createElement("div")),m_node.attr("id",m_name),m_node.css("position","absolute"),m_node.css("width","100%"),m_node.css("height","100%"),m_this.opacity(m_opacity),m_this.zIndex(m_zIndex),m_this},geo.layer.newLayerId=function(){"use strict";var currentId=1;return function(){var id=currentId;return currentId+=1,id}}(),geo.layer.create=function(map,spec){"use strict";if(spec=spec||{},spec.type="feature","feature"!==spec.type)return console.warn("Unsupported layer type"),null;if(spec.renderer=spec.renderer||"vgl","d3"!==spec.renderer&&"vgl"!==spec.renderer)return console.warn("Invalid renderer"),null;var layer=map.createLayer(spec.type,spec);return layer?(spec.features.forEach(function(f){f.data=f.data||spec.data,f.feature=geo.feature.create(layer,f)}),layer):(console.warn("Unable to create a layer"),null)},inherit(geo.layer,geo.sceneObject),geo.featureLayer=function(arg){"use strict";if(!(this instanceof geo.featureLayer))return new geo.featureLayer(arg);
+geo.layer.call(this,arg);var m_this=this,m_features=[],s_init=this._init,s_exit=this._exit,s_update=this._update,s_draw=this.draw;return this.createFeature=function(featureName,arg){var newFeature=geo.createFeature(featureName,m_this,m_this.renderer(),arg);return m_this.addChild(newFeature),m_features.push(newFeature),m_this.features(m_features),m_this.modified(),newFeature},this.deleteFeature=function(feature){var i;for(i=0;im_this.updateTime().getMTime())for(i=0;i=deltaT?30:deltaT;var sf=springForce(),speed=calcSpeed(v),vx=v.x/speed,vy=v.y/speed;return speed*=Math.exp(-m_options.momentum.drag*deltaT),calcSpeed(sf)*deltaT+speed0?(vx*=speed,vy*=speed):(vx=0,vy=0),{x:vx-sf.x*deltaT,y:vy-sf.y*deltaT})}function springForce(){var xplus,xminus,yplus,yminus;if(!m_options.spring.enabled)return{x:0,y:0};var ul=m_this.map().gcsToDisplay({x:-180,y:82}),lr=m_this.map().gcsToDisplay({x:180,y:-82}),c=m_options.spring.springConstant,width=m_this.map().node().width(),height=m_this.map().node().height();return xplus=c*Math.max(0,ul.x),xminus=c*Math.max(0,width-lr.x),yplus=c*Math.max(0,ul.y)/2,yminus=c*Math.max(0,height-lr.y)/2,{x:xplus-xminus,y:yplus-yminus}}if(!(this instanceof geo.mapInteractor))return new geo.mapInteractor(args);geo.object.call(this);var m_mouse,m_keyboard,m_state,$node,m_options=args||{},m_this=this,m_wheelQueue={x:0,y:0},m_throttleTime=10,m_wait=!1,m_disableThrottle=!0,m_selectionLayer=null,m_selectionPlane=null,m_paused=!1,m_clickMaybe=!1;return m_options=$.extend(!0,{panMoveButton:"left",panMoveModifiers:{},zoomMoveButton:"right",zoomMoveModifiers:{},panWheelEnabled:!1,panWheelModifiers:{},zoomWheelEnabled:!0,zoomWheelModifiers:{},wheelScaleX:1,wheelScaleY:1,zoomScale:1,selectionButton:"left",selectionModifiers:{shift:!0},momentum:{enabled:!0,maxSpeed:2.5,minSpeed:.01,drag:.01},spring:{enabled:!1,springConstant:5e-5},click:{enabled:!0,buttons:{left:!0,right:!0,middle:!0},duration:0,cancelOnMove:!0}},m_options),m_mouse={page:{x:0,y:0},map:{x:0,y:0},buttons:{left:!1,right:!1,middle:!1},modifiers:{alt:!1,ctrl:!1,shift:!1,meta:!1},time:new Date,deltaTime:1,velocity:{x:0,y:0}},m_keyboard={},m_state={},this._connectEvents=function(){return m_options.map?(m_this._disconnectEvents(),$node=$(m_options.map.node()),$node.on("mousemove.geojs",m_this._handleMouseMove),$node.on("mousedown.geojs",m_this._handleMouseDown),$node.on("mouseup.geojs",m_this._handleMouseUp),$node.on("wheel.geojs",m_this._handleMouseWheel),$node.on("dragstart",function(){return!1}),("right"===m_options.panMoveButton||"right"===m_options.zoomMoveButton)&&$node.on("contextmenu.geojs",function(){return!1}),m_this):m_this},this._disconnectEvents=function(){return $node&&($node.off(".geojs"),$node=null),m_this},this.map=function(val){return void 0!==val?(m_options.map=val,m_this._connectEvents(),m_this):m_options.map},this.options=function(opts){return void 0===opts?$.extend({},m_options):($.extend(m_options,opts),m_this)},this._getMousePosition=function(evt){var dt,t,offset=$node.offset();t=(new Date).valueOf(),dt=t-m_mouse.time,m_mouse.time=t,m_mouse.deltaTime=dt,m_mouse.velocity={x:(evt.pageX-m_mouse.page.x)/dt,y:(evt.pageY-m_mouse.page.y)/dt},m_mouse.page={x:evt.pageX,y:evt.pageY},m_mouse.map={x:evt.pageX-offset.left,y:evt.pageY-offset.top};try{m_mouse.geo=m_this.map().displayToGcs(m_mouse.map)}catch(e){m_mouse.geo=null}},this._getMouseButton=function(evt){1===evt.which?m_mouse.buttons.left="mouseup"!==evt.type:3===evt.which?m_mouse.buttons.right="mouseup"!==evt.type:2===evt.which&&(m_mouse.buttons.middle="mouseup"!==evt.type)},this._getMouseModifiers=function(evt){m_mouse.modifiers.alt=evt.altKey,m_mouse.modifiers.ctrl=evt.ctrlKey,m_mouse.modifiers.meta=evt.metaKey,m_mouse.modifiers.shift=evt.shiftKey},this._getSelection=function(){var origin=m_state.origin,mouse=m_this.mouse(),map=m_this.map(),display={},gcs={};return display.upperLeft={x:Math.min(origin.map.x,mouse.map.x),y:Math.min(origin.map.y,mouse.map.y)},display.lowerRight={x:Math.max(origin.map.x,mouse.map.x),y:Math.max(origin.map.y,mouse.map.y)},display.upperRight={x:display.lowerRight.x,y:display.upperLeft.y},display.lowerLeft={x:display.upperLeft.x,y:display.lowerRight.y},gcs.upperLeft=map.displayToGcs(display.upperLeft),gcs.lowerRight=map.displayToGcs(display.lowerRight),gcs.upperRight=map.displayToGcs(display.upperRight),gcs.lowerLeft=map.displayToGcs(display.lowerLeft),m_selectionPlane.origin([display.lowerLeft.x,display.lowerLeft.y,0]),m_selectionPlane.upperLeft([display.upperLeft.x,display.upperLeft.y,0]),m_selectionPlane.lowerRight([display.lowerRight.x,display.lowerRight.y,0]),m_selectionPlane.draw(),{display:display,gcs:gcs,mouse:mouse,origin:$.extend({},m_state.origin)}},this.cancel=function(action){var out;return out=action?m_state.action===action:!!m_state.action,out&&(m_state={}),out},this._handleMouseDown=function(evt){var action=null;m_paused||("momentum"===m_state.action&&(m_state={}),m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),!m_options.click.enabled||m_mouse.buttons.left&&!m_options.click.buttons.left||m_mouse.buttons.right&&!m_options.click.buttons.right||m_mouse.buttons.middle&&!m_options.click.buttons.middle||(m_clickMaybe=!0,m_options.click.duration>0&&window.setTimeout(function(){m_clickMaybe=!1},m_options.click.duration)),eventMatch(m_options.panMoveButton,m_options.panMoveModifiers)?action="pan":eventMatch(m_options.zoomMoveButton,m_options.zoomMoveModifiers)?action="zoom":eventMatch(m_options.selectionButton,m_options.selectionModifiers)&&(action="select"),m_mouse.velocity={x:0,y:0},action&&(m_state={action:action,origin:$.extend(!0,{},m_mouse),delta:{x:0,y:0}},"select"===action&&(m_selectionLayer&&(m_selectionLayer.clear(),m_this.map().deleteLayer(m_selectionLayer),m_selectionLayer=null),m_selectionLayer=m_this.map().createLayer("feature",{renderer:"d3"}),m_selectionPlane=m_selectionLayer.createFeature("plane"),m_selectionPlane.style({screenCoordinates:!0,fillOpacity:function(){return.25}}),m_this.map().geoTrigger(geo.event.brushstart,m_this._getSelection())),$(document).on("mousemove.geojs",m_this._handleMouseMoveDocument),$(document).on("mouseup.geojs",m_this._handleMouseUpDocument)))},this._handleMouseMove=function(evt){m_paused||m_state.action||(m_options.click.cancelOnMove&&(m_clickMaybe=!1),m_clickMaybe||(m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_this.map().geoTrigger(geo.event.mousemove,m_this.mouse())))},this._handleMouseMoveDocument=function(evt){var dx,dy,selectionObj;if(!m_paused&&(m_this._getMousePosition(evt),m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_options.click.cancelOnMove&&(m_clickMaybe=!1),!m_clickMaybe))return m_state.action?void(doRespond()&&(dx=m_mouse.map.x-m_state.origin.map.x-m_state.delta.x,dy=m_mouse.map.y-m_state.origin.map.y-m_state.delta.y,m_state.delta.x+=dx,m_state.delta.y+=dy,"pan"===m_state.action?m_this.map().pan({x:dx,y:dy}):"zoom"===m_state.action?m_this.map().zoom(m_this.map().zoom()-dy*m_options.zoomScale/120,m_state):"select"===m_state.action&&(selectionObj=m_this._getSelection(),m_this.map().geoTrigger(geo.event.brush,selectionObj)),evt.preventDefault())):void console.log("WARNING: Invalid state in mapInteractor.")},this._handleMouseUpDocument=function(evt){var selectionObj,oldAction;m_paused||(m_clickMaybe=!1,m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),$(document).off(".geojs"),m_mouse.buttons.right&&evt.preventDefault(),"select"===m_state.action&&(selectionObj=m_this._getSelection(),m_selectionLayer.clear(),m_this.map().deleteLayer(m_selectionLayer),m_selectionLayer=null,m_selectionPlane=null,m_this.map().geoTrigger(geo.event.brushend,selectionObj)),oldAction=m_state.action,m_state={},m_options.momentum.enabled&&"pan"===oldAction&&m_this.springBack(!0))},this._handleMouseUp=function(evt){m_paused||m_clickMaybe&&m_this._handleMouseClick(evt)},this._handleMouseClick=function(evt){m_this._getMouseButton(evt),m_this._getMouseModifiers(evt),m_this.cancel("pan"),$(document).off(".geojs"),m_clickMaybe=!1,m_this.map().geoTrigger(geo.event.mouseclick,m_this.mouse())},this._handleMouseWheel=function(evt){var zoomFactor;if(!m_paused){if(evt.deltaFactor=1,1===evt.originalEvent.deltaMode?evt.deltaFactor=12:2===evt.originalEvent.deltaMode&&(evt.deltaFactor=$(window).height()),evt.deltaX=evt.originalEvent.deltaX||0,evt.deltaY=evt.originalEvent.deltaY||0,m_this._getMouseModifiers(evt),evt.deltaX=evt.deltaX*m_options.wheelScaleX*evt.deltaFactor/120,evt.deltaY=evt.deltaY*m_options.wheelScaleY*evt.deltaFactor/120,evt.preventDefault(),!doRespond())return m_wheelQueue.x+=evt.deltaX,void(m_wheelQueue.y+=evt.deltaY);evt.deltaX+=m_wheelQueue.x,evt.deltaY+=m_wheelQueue.y,m_wheelQueue={x:0,y:0},m_options.panWheelEnabled&&eventMatch("wheel",m_options.panWheelModifiers)?m_this.map().pan({x:evt.deltaX,y:-evt.deltaY}):m_options.zoomWheelEnabled&&eventMatch("wheel",m_options.zoomWheelModifiers)&&(zoomFactor=-evt.deltaY,m_this.map().zoom(m_this.map().zoom()+zoomFactor,m_mouse))}},this.springBack=function(initialVelocity){"momentum"!==m_state.action&&(initialVelocity||(m_mouse.velocity={x:0,y:0}),m_state.action="momentum",m_state.origin=m_this.mouse(),m_state.start=new Date,m_state.handler=function(){var v,s,last,dt;if(dt=Math.min(m_mouse.deltaTime,30),"momentum"===m_state.action&&m_this.map()&&!m_this.map().transition()){if(last=m_state.start.valueOf(),m_state.start=new Date,v=modifyVelocity(m_mouse.velocity,m_state.start-last),!v)return void(m_state={});s=calcSpeed(v),s>m_options.momentum.maxSpeed&&(s=m_options.momentum.maxSpeed/s,v.x=v.x*s,v.y=v.y*s),isFinite(v.x)&&isFinite(v.y)||(v.x=0,v.y=0),m_mouse.velocity.x=v.x,m_mouse.velocity.y=v.y,m_this.map().pan({x:m_mouse.velocity.x*dt,y:m_mouse.velocity.y*dt}),m_state.handler&&window.requestAnimationFrame(m_state.handler)}},m_state.handler&&window.requestAnimationFrame(m_state.handler))},this._handleDoubleClick=function(){},this.destroy=function(){m_this._disconnectEvents(),m_this.map(null)},this.mouse=function(){return $.extend(!0,{},m_mouse)},this.keyboard=function(){return $.extend(!0,{},m_keyboard)},this.state=function(){return $.extend(!0,{},m_state)},this.pause=function(value){return void 0===value?m_paused:(m_paused=!!value,m_this)},this.simulateEvent=function(type,options){var evt,page,offset,which;return m_this.map()?(page=options.page||{},options.map&&(offset=$node.offset(),page.x=options.map.x+offset.left,page.y=options.map.y+offset.top),"left"===options.button?which=1:"right"===options.button?which=3:"middle"===options.button&&(which=2),options.modifiers=options.modifiers||[],options.wheelDelta=options.wheelDelta||{},evt=$.Event(type,{pageX:page.x,pageY:page.y,which:which,altKey:options.modifiers.indexOf("alt")>=0,ctrlKey:options.modifiers.indexOf("ctrl")>=0,metaKey:options.modifiers.indexOf("meta")>=0,shiftKey:options.modifiers.indexOf("shift")>=0,originalEvent:{deltaX:options.wheelDelta.x,deltaY:options.wheelDelta.y,deltaMode:options.wheelMode}}),void $node.trigger(evt)):m_this},this._connectEvents(),this},inherit(geo.mapInteractor,geo.object),geo.clock=function(opts){"use strict";if(!(this instanceof geo.clock))return new geo.clock(opts);opts=opts||{},geo.object.call(this,opts);var m_this=this,m_now=new Date(0),m_start=null,m_end=null,m_step=null,m_rate=null,m_loop=Number.POSITIVE_INFINITY,m_currentLoop=0,m_state="stop",m_currentAnimation=null,m_object=null;this.object=function(arg){return void 0===arg?m_object:(m_object=arg,m_this)},this._attached=function(){return m_object instanceof geo.object},this.now=function(arg){var previous=m_now;return void 0===arg?m_now:(m_now=arg,m_now!==previous&&m_this._attached()&&m_this.object().geoTrigger(geo.event.clock.change,{previous:previous,current:m_now,clock:m_this}),m_this)},this.start=function(arg){return void 0===arg?m_start:(m_start=arg,m_this)},this.end=function(arg){return void 0===arg?m_end:(m_end=arg,m_this)},this.step=function(arg){return void 0===arg?m_step:(m_step=arg,m_this)},this.loop=function(arg){return void 0===arg?m_loop:(m_loop=arg,m_this)},this.state=function(arg,step){return void 0===arg?m_state:["stop","play","pause"].indexOf(arg)<0?(console.log("WARNING: Ignored invalid state: "+arg),m_this):("play"===arg&&"stop"===m_state&&(m_currentLoop=0,m_this.now(m_this.start())),"play"===arg&&"play"!==m_state&&(m_state=arg,m_this._animate(step||1)),m_state=arg,m_this)},this.framerate=function(arg){return void 0===arg?m_rate:(m_rate=arg,m_this)},this.stepForward=function(){return m_this.state("pause"),m_this._setNextFrame(1),m_this},this.stepBackward=function(){return m_this.state("pause"),m_this._setNextFrame(-1),m_this},this._setNextFrame=function(step){var next=new Date(m_this.now().valueOf()+step*m_this.step());return next>=m_this.end()||next<=m_this.start()?m_this.loop()<=m_currentLoop?void m_this.state("stop"):(m_currentLoop+=1,void(step>=0?m_this.now(m_this.start()):m_this.now(m_this.end()))):void m_this.now(next)},this._animate=function(step){function frame(){myAnimation===m_currentAnimation&&(m_this._setNextFrame(step),"play"===m_this.state()?m_this.framerate()?window.setTimeout(frame,1e3/m_this.framerate()):window.requestAnimationFrame(frame):m_this._attached()&&m_this.object().geoTrigger(geo.event.clock[m_this.state()],{current:m_this.now(),clock:m_this}))}var myAnimation={};m_currentAnimation=myAnimation,m_this._attached()&&m_this.object().geoTrigger(geo.event.clock.play,{current:m_this.now(),clock:m_this}),m_this.framerate()?window.setTimeout(frame,1e3/m_this.framerate()):window.requestAnimationFrame(frame)}},inherit(geo.clock,geo.object),function(){"use strict";geo.tile=function(spec){return this instanceof geo.tile?(this._index=spec.index,this._size=spec.size,this._overlap=spec.overlap||{x:0,y:0},this._wrap=spec.wrap||{x:1,y:1},this._url=spec.url,this._fetched=!1,Object.defineProperty(this,"index",{get:function(){return this._index}}),Object.defineProperty(this,"size",{get:function(){return this._size}}),Object.defineProperty(this,"overlap",{get:function(){return this._overlap}}),this.fetch=function(){return this._fetched||$.get(this._url).promise(this),this},this.then=function(onSuccess,onFailure){return this.fetch(),this.then(onSuccess,onFailure),this},this["catch"]=function(method){return this.then(void 0,method),this},this.toString=function(){return[this._index.level||0,this._index.y,this._index.x].join("_")},this.bounds=function(index,shift){var left,right,bottom,top;return left=this.size.x*(this.index.x-index.x)-this.overlap.x-shift.x,right=left+this.size.x+2*this.overlap.x,top=this.size.y*(this.index.y-index.y)-this.overlap.y-shift.y,bottom=top+this.size.y+2*this.overlap.y,{left:left,right:right,bottom:bottom,top:top}},Object.defineProperty(this,"bottom",{get:function(){return this.size.y*(this.index.y+1)+this.overlap.y}}),Object.defineProperty(this,"top",{get:function(){return this.size.y*this.index.y-this.overlap.y}}),Object.defineProperty(this,"left",{get:function(){return this.size.x*this.index.x-this.overlap.x}}),Object.defineProperty(this,"right",{get:function(){return this.size.x*(this.index.x+1)+this.overlap.x}}),Object.defineProperty(this,"levelSize",{value:{width:Math.pow(2,this.index.level||0)*this.size.x,height:Math.pow(2,this.index.level||0)*this.size.y}}),void(this.fadeIn=function(duration){return $.noop(duration),this})):new geo.tile(spec)}}(),function(){"use strict";geo.imageTile=function(spec){return this instanceof geo.imageTile?(spec.size=spec.size||{x:256,y:256},this._image=null,this._cors=spec.crossDomain||"anonymous",geo.tile.call(this,spec),Object.defineProperty(this,"image",{get:function(){return this._image}}),this.fetch=function(){var defer;return this._image||(this._image=new Image(this.size.x,this.size.y),this._url.indexOf(":")>=0&&this._url.indexOf("/")>=0&&this._url.indexOf(":")n;)this.remove(this._atime[this._atime.length-1]);this._size=n}}),Object.defineProperty(this,"length",{get:function(){return this._atime.length}}),this._access=function(hash){return this._atime.indexOf(hash)},this.remove=function(tile){var hash="string"==typeof tile?tile:tile.toString();return hash in this._cache?(this._atime.splice(this._access(hash),1),delete this._cache[hash],!0):!1},this.clear=function(){return this._cache={},this._atime=[],this},this.get=function(hash){return hash="string"==typeof hash?hash:hash.toString(),hash in this._cache?(this._atime.splice(this._access(hash),1),this._atime.unshift(hash),this._cache[hash]):null},this.add=function(tile){this.remove(tile);var hash=tile.toString();for(this._cache[hash]=tile,this._atime.unshift(hash);this._atime.length>this.size;)hash=this._atime.pop(),delete this._cache[hash]},this.clear(),this):new geo.tileCache(options)}}(),function(){"use strict";function modulo(a,b){return(a%b+b)%b}function m_getTileSubdomain(x,y,subdomains){return subdomains[modulo(x+y,subdomains.length)]}function m_tileUrlFromTemplate(base){return function(x,y,z,subdomains){return base.replace("{s}",m_getTileSubdomain(x,y,subdomains)).replace("{z}",z).replace("{x}",x).replace("{y}",y)}}geo.tileLayer=function(options){if(!(this instanceof geo.tileLayer))return new geo.tileLayer(options);if(geo.featureLayer.call(this,options),options=$.extend(!0,{},this.constructor.defaults,options||{}),options.cacheSize||(options.cacheSize=options.keepLower?400:200),"string"===$.type(options.subdomains)&&(options.subdomains=options.subdomains.split("")),options.baseUrl){var url=options.baseUrl;url&&"/"!==url.charAt(url.length-1)&&(url+="/"),options.url=url+"{z}/{x}/{y}."+(options.imageFormat||"png")}options.originalUrl=options.url,"string"===$.type(options.url)&&(options.url=m_tileUrlFromTemplate(options.url));var lastZoom=null,lastX=null,lastY=null,s_init=this._init,_deferredPurge=null;return this._options=$.extend(!0,{},options),this.attribution(options.attribution),this._activeTiles={},this._tileTree={},this._cache=geo.tileCache({size:options.cacheSize}),Object.defineProperty(this,"options",{get:function(){return $.extend({},this._options)}}),Object.defineProperty(this,"cache",{get:function(){return this._cache}}),Object.defineProperty(this,"activeTiles",{get:function(){return $.extend({},this._activeTiles)}}),this.tilesAtZoom=function(level){var s=Math.pow(2,level);return{x:s,y:s}},this.isValid=function(index){return this._options.minLevel<=index.level&&index.level<=this._options.maxLevel&&(this._options.wrapX||0<=index.x&&index.x<=this.tilesAtZoom(index.level).x-1)&&(this._options.wrapY||0<=index.y&&index.y<=this.tilesAtZoom(index.level).y-1)?!0:!1},this._origin=function(level){var index,offset,origin=this.toLevel(this.toLocal(this.map().origin()),level),o=this._options;return index={x:Math.floor(origin.x/o.tileWidth),y:Math.floor(origin.y/o.tileHeight)},offset={x:origin.x-o.tileWidth*index.x,y:origin.y-o.tileHeight*index.y},{index:index,offset:offset}},this._tileBounds=function(tile){var origin=this._origin(tile.index.level);return tile.bounds(origin.index,origin.offset)},this.tileAtPoint=function(point,level){var o=this._origin(level),map=this.map();point=this.displayToLevel(map.gcsToDisplay(point,null),level);var to=this._options.tileOffset(level);to&&(point.x+=to.x,point.y+=to.y);var tile={x:Math.floor(o.index.x+(o.offset.x+point.x)/this._options.tileWidth),y:Math.floor(o.index.y+(o.offset.y+point.y)/this._options.tileHeight)};return tile},this.gcsTileBounds=function(indexOrTile,gcs){var tile=indexOrTile.index?indexOrTile:geo.tile({index:indexOrTile,size:{x:this._options.tileWidth,y:this._options.tileHeight},url:""}),to=this._options.tileOffset(tile.index.level),bounds=tile.bounds({x:0,y:0},to),map=this.map(),unit=map.unitsPerPixel(tile.index.level),coord=[{x:bounds.left*unit,y:this._topDown()*bounds.top*unit},{x:bounds.right*unit,y:this._topDown()*bounds.bottom*unit}];return gcs=null===gcs?map.gcs():void 0===gcs?map.ingcs():gcs,gcs!==map.gcs()&&(coord=geo.transform.transformCoordinates(gcs,map.gcs(),coord)),{left:coord[0].x,top:coord[0].y,right:coord[1].x,bottom:coord[1].y}},this._getTile=function(index,source){var urlParams=source||index;return geo.tile({index:index,size:{x:this._options.tileWidth,y:this._options.tileHeight},url:this._options.url(urlParams.x,urlParams.y,urlParams.level||0,this._options.subdomains)})},this._getTileCached=function(index,source){var tile=this.cache.get(this._tileHash(index));return null===tile&&(tile=this._getTile(index,source),this.cache.add(tile)),tile},this._tileHash=function(index){return[index.level||0,index.y,index.x].join("_")},this._getTileRange=function(level,bounds){return{start:this.tileAtPoint({x:bounds.left,y:bounds.top},level),end:this.tileAtPoint({x:bounds.right,y:bounds.bottom},level)}},this._getTiles=function(maxLevel,bounds,sorted){var i,j,index,nTilesLevel,start,end,indexRange,source,center,level,tiles=[],minLevel=this._options.keepLower?0:maxLevel;for(level=minLevel;maxLevel>=level;level+=1)for(indexRange=this._getTileRange(level,bounds),start=indexRange.start,end=indexRange.end,nTilesLevel=this.tilesAtZoom(level),index={level:level},index.nx=nTilesLevel.x,index.ny=nTilesLevel.y,i=start.x;i<=end.x;i+=1)for(index.x=i,j=start.y;j<=end.y;j+=1)index.y=j,source=$.extend({},index),this._options.wrapX&&(source.x=modulo(index.x,index.nx)),this._options.wrapY&&(source.y=modulo(index.y,index.ny)),this.isValid(source)&&tiles.push(this._getTileCached($.extend({},index),source));return sorted&&(center={x:(start.x+end.x)/2,y:(start.y+end.y)/2},tiles.sort(this._loadMetric(center))),tiles},this.prefetch=function(level,bounds){var tiles;return tiles=this._getTiles(level,bounds,!0),$.when.apply($,tiles.map(function(tile){return tile.fetch()}))},this._loadMetric=function(center){return function(a,b){var a0,b0,dx,dy,cx,cy,scale;return a.level!==b.level?b.level-a.level:(scale=Math.pow(2,a.level-center.level),cx=center.x*scale,cy=center.y*scale,dx=a.x-cx,dy=a.y-cy,a0=dx*dx+dy*dy,dx=b.x-cx,dy=b.y-cy,b0=dx*dx+dy*dy,a0-b0)}},this.fromLevel=function(coord,level){var s=Math.pow(2,-level);return{x:coord.x*s,y:coord.y*s}},this.toLevel=function(coord,level){var s=Math.pow(2,level);return{x:coord.x*s,y:coord.y*s}},this.drawTile=function(tile){var hash=tile.toString();this._activeTiles.hasOwnProperty(hash)?this._moveToTop(tile):this._drawTile(tile),this._activeTiles[hash]=tile},this._drawTile=function(tile){if(null!==this.renderer())throw new Error("This draw method is not valid on renderer managed layers.");var div=$(this._getSubLayer(tile.index.level)),bounds=this._tileBounds(tile),duration=this._options.animationDuration,container=$('').attr("tile-reference",tile.toString());container.append(tile.image),container.css({position:"absolute",left:bounds.left+"px",top:bounds.top+"px"}),duration>0&&tile.fadeIn(duration),div.append(container),tile["catch"](function(){console.warn("Could not load tile at "+tile.index),this._remove(tile)}.bind(this))},this.remove=function(tile){var hash=tile.toString(),value=this._activeTiles[hash];return value instanceof geo.tile&&this._remove(value),delete this._activeTiles[hash],value},this._remove=function(tile){tile.image&&(tile.image.parentElement?$(tile.image.parentElement).remove():console.log("No parent element to remove "+tile.toString(),tile),$(tile.image).remove())},this._moveToTop=function(tile){$.noop(tile)},this._getViewBounds=function(){var map=this.map(),mapZoom=map.zoom(),zoom=this._options.tileRounding(mapZoom),scale=Math.pow(2,mapZoom-zoom),size=map.size(),ul=this.displayToLevel({x:0,y:0}),lr=this.displayToLevel({x:size.width,y:size.height});return{level:zoom,scale:scale,left:ul.x,right:lr.x,bottom:lr.y,top:ul.y}},this._purge=function(zoom,doneLoading){var tile,hash,bounds={};if(!this._updating){bounds=this._getViewBounds();for(hash in this._activeTiles)tile=this._activeTiles[hash],this._canPurge(tile,bounds,zoom,doneLoading)&&this.remove(tile);return this}},this.clear=function(){var tile,tiles=[];for(tile in this._activeTiles)tiles.push(this.remove(tile));return this._tileTree={},tiles},this.reset=function(){this.clear(),this._cache.clear()},this.toLocal=function(pt,zoom){var map=this.map(),unit=map.unitsPerPixel(void 0===zoom?map.zoom():zoom);return{x:pt.x/unit,y:this._topDown()*pt.y/unit}},this.fromLocal=function(pt,zoom){var map=this.map(),unit=map.unitsPerPixel(void 0===zoom?map.zoom():zoom);return{x:pt.x*unit,y:this._topDown()*pt.y*unit}},this._topDown=function(){return this._options.topDown?1:-1},this._getSubLayer=function(level){var node=this.canvas().find("div[data-tile-layer="+level.toFixed()+"]").get(0);return node||(node=$('').css("transform-origin","0px").get(0),this.canvas().append(node)),node},this._updateSubLayers=function(level){this.canvas().find(".geo-tile-layer").each(function(idx,el){var $el=$(el),layer=parseInt($el.data("tileLayer"));$el.css("transform","scale("+Math.pow(2,level-layer)+")")}.bind(this))},this._update=function(){var tiles,map=this.map(),mapZoom=map.zoom(),zoom=this._options.tileRounding(mapZoom),center=this.displayToLevel(void 0,zoom),bounds=map.bounds(void 0,null),view=this._getViewBounds(),myPurge={};_deferredPurge=myPurge,tiles=this._getTiles(zoom,bounds,!0),this._updateSubLayers(zoom);var to=this._options.tileOffset(zoom);null===this.renderer()&&(this.canvas().css("transform-origin","center center"),this.canvas().css("transform","scale("+Math.pow(2,mapZoom-zoom)+")translate("+-to.x+"px,"+-to.y+"px)translate("+map.size().width/2+"px,"+map.size().height/2+"px)translate("+-(view.left+view.right)/2+"px,"+-(view.bottom+view.top)/2+"px)")),this.canvas().attr({scale:Math.pow(2,mapZoom-zoom),dx:-to.x+-(view.left+view.right)/2,dy:-to.y+-(view.bottom+view.top)/2}),lastZoom=mapZoom,lastX=center.x,lastY=center.y,this._tileTree={},tiles.forEach(function(tile){tile.then(function(){if(tile===this.cache.get(tile.toString())){var mapZoom=map.zoom(),zoom=this._options.tileRounding(mapZoom),bounds=this._getViewBounds();if(this._canPurge(tile,bounds,zoom))return void this.remove(tile);this.drawTile(tile),this._setTileTree(tile)}}.bind(this)),this.addPromise(tile)}.bind(this)),$.when.apply($,tiles).done(function(){_deferredPurge===myPurge&&this._purge(zoom,!0)}.bind(this))},this._setTileTree=function(tile){var index=tile.index;this._tileTree[index.level]=this._tileTree[index.level]||{},this._tileTree[index.level][index.x]=this._tileTree[index.level][index.x]||{},this._tileTree[index.level][index.x][index.y]=tile},this._getTileTree=function(index){return((this._tileTree[index.level]||{})[index.x]||{})[index.y]||null},this._isCovered=function(tile){var level=tile.index.level,x=tile.index.x,y=tile.index.y,tiles=[];return(tiles=this._getTileTree({level:level-1,x:Math.floor(x/2),y:Math.floor(y/2)}))?[tiles]:(tiles=[this._getTileTree({level:level+1,x:2*x,y:2*y}),this._getTileTree({level:level+1,x:2*x+1,y:2*y}),this._getTileTree({level:level+1,x:2*x,y:2*y+1}),this._getTileTree({level:level+1,x:2*x+1,y:2*y+1})],tiles.every(function(t){return null!==t})?tiles:null)},this._outOfBounds=function(tile,bounds){var to=this._options.tileOffset(tile.index.level),scale=1;return tile.index.level!==bounds.level&&(scale=Math.pow(2,(bounds.level||0)-(tile.index.level||0))),(tile.bottom-to.y)*scalebounds.right||(tile.top-to.y)*scale>bounds.bottom||(tile.right-to.x)*scalear_vp?(sclx=1,scly=ar_bds/ar_vp):(scly=1,sclx=ar_vp/ar_bds),{x:sclx,y:scly}}function calculate_zoom(bounds){var z,scl=camera_scaling(bounds);return z=scl.y>scl.x?-Math.log2(Math.abs(bounds.right-bounds.left)*scl.x/(m_width*m_unitsPerPixel)):-Math.log2(Math.abs(bounds.top-bounds.bottom)*scl.y/(m_height*m_unitsPerPixel))}function reset_minimum_zoom(){m_clampZoom?m_validZoomRange.min=Math.max(m_validZoomRange.origMin,calculate_zoom(m_maxBounds)):m_validZoomRange.min=m_validZoomRange.origMin}function fix_zoom(zoom,ignoreDiscreteZoom){return zoom=Math.max(Math.min(m_validZoomRange.max,zoom),m_validZoomRange.min),m_discreteZoom&&!ignoreDiscreteZoom&&(zoom=Math.round(zoom),zoomm_maxBounds.right-m_maxBounds.left?dx=m_maxBounds.left-(bounds.right-bounds.left-(m_maxBounds.right-m_maxBounds.left))/2-bounds.left:bounds.leftm_maxBounds.right&&(dx=m_maxBounds.right-bounds.right),dx&&(bounds={left:bounds.left+=dx,right:bounds.right+=dx,top:bounds.top,bottom:bounds.bottom})),m_clampBoundsY&&(bounds.top-bounds.bottom>m_maxBounds.top-m_maxBounds.bottom?dy=m_maxBounds.bottom-(bounds.top-bounds.bottom-(m_maxBounds.top-m_maxBounds.bottom))/2-bounds.bottom:bounds.top>m_maxBounds.top?dy=m_maxBounds.top-bounds.top:bounds.bottom=m_transition.end.time||next)return next||(m_this.center(m_transition.end.center,null),m_this.zoom(m_transition.end.zoom)),m_transition=null,m_this.geoTrigger(geo.event.transitionend,defaultOpts),done&&done(),void(next&&(m_queuedTransition=null,m_this.transition(next)));var z=m_transition.ease((time-m_transition.start.time)/defaultOpts.duration),p=m_transition.interp(z);m_transition.zCoord&&(p[2]=z2zoom(p[2])),m_this.center({x:p[0],y:p[1]},null),m_this.zoom(p[2],void 0,!0),window.requestAnimationFrame(anim)}if(void 0===opts)return m_transition;if(m_transition)return m_queuedTransition=opts,m_this;var units=m_this.unitsPerPixel(0),defaultOpts={center:m_this.center(void 0,null),zoom:m_this.zoom(),duration:1e3,ease:function(t){return t},interp:defaultInterp,done:null,zCoord:!0};return opts.center&&(gcs=null===gcs?m_gcs:void 0===gcs?m_ingcs:gcs,opts.center=geo.util.normalizeCoordinates(opts.center),gcs!==m_gcs&&(opts.center=geo.transform.transformCoordinates(gcs,m_gcs,[opts.center])[0])),$.extend(defaultOpts,opts),m_transition={start:{center:m_this.center(void 0,null),zoom:m_this.zoom()},end:{center:defaultOpts.center,zoom:fix_zoom(defaultOpts.zoom)},ease:defaultOpts.ease,zCoord:defaultOpts.zCoord,done:defaultOpts.done,duration:defaultOpts.duration},defaultOpts.zCoord?m_transition.interp=defaultOpts.interp([m_transition.start.center.x,m_transition.start.center.y,zoom2z(m_transition.start.zoom)],[m_transition.end.center.x,m_transition.end.center.y,zoom2z(m_transition.end.zoom)]):m_transition.interp=defaultOpts.interp([m_transition.start.center.x,m_transition.start.center.y,m_transition.start.zoom],[m_transition.end.center.x,m_transition.end.center.y,m_transition.end.zoom]),m_this.geoTrigger(geo.event.transitionstart,defaultOpts),defaultOpts.cancelNavigation?(m_this.geoTrigger(geo.event.transitionend,defaultOpts),m_this):(defaultOpts.cancelAnimation?(defaultOpts.duration=0,anim(0)):window.requestAnimationFrame(anim),m_this)},this.bounds=function(bds,gcs){var nav;if(gcs=null===gcs?m_gcs:void 0===gcs?m_ingcs:gcs,void 0!==bds){if(gcs!==m_gcs){var trans=geo.transform.transformCoordinates(gcs,m_gcs,[{x:bds.left,y:bds.top},{x:bds.right,y:bds.bottom}]);bds={left:trans[0].x,top:trans[0].y,right:trans[1].x,bottom:trans[1].y}}bds=fix_bounds(bds),nav=m_this.zoomAndCenterFromBounds(bds,null),m_this.zoom(nav.zoom),m_this.center(nav.center,null)}return m_this.boundsFromZoomAndCenter(m_zoom,m_center,gcs)},this.zoomAndCenterFromBounds=function(bounds,gcs){var center,zoom;if(gcs=null===gcs?m_gcs:void 0===gcs?m_ingcs:gcs,gcs!==m_gcs){var trans=geo.transform.transformCoordinates(gcs,m_gcs,[{x:bounds.left,y:bounds.top},{x:bounds.right,y:bounds.bottom}]);bounds={left:trans[0].x,top:trans[0].y,right:trans[1].x,bottom:trans[1].y}}if(bounds.left>=bounds.right||bounds.bottom>=bounds.top)throw new Error("Invalid bounds provided");return zoom=fix_zoom(calculate_zoom(bounds)),bounds=fix_bounds(bounds),center={x:(bounds.left+bounds.right)/2-m_origin.x,y:(bounds.top+bounds.bottom)/2-m_origin.y},{zoom:zoom,center:center}},this.boundsFromZoomAndCenter=function(zoom,center,gcs){var width,height,bounds,units;return gcs=null===gcs?m_gcs:void 0===gcs?m_ingcs:gcs,zoom=fix_zoom(zoom),units=m_this.unitsPerPixel(zoom),center=m_this.gcsToWorld(center,gcs),width=m_width*units/2,height=m_height*units/2,bounds={left:center.x-width+m_origin.x,right:center.x+width+m_origin.x,bottom:center.y-height+m_origin.y,top:center.y+height+m_origin.y},fix_bounds(bounds)},this.discreteZoom=function(discreteZoom){return void 0===discreteZoom?m_discreteZoom:(discreteZoom=discreteZoom?!0:!1,m_discreteZoom!==discreteZoom&&(m_discreteZoom=discreteZoom,m_discreteZoom&&m_this.zoom(Math.round(m_this.zoom()))),m_this)},this.layers=this.children,this.updateAttribution=function(){m_this.node().find(".geo-attribution").remove();var $a=$("").addClass("geo-attribution").css({position:"absolute",right:"0px",bottom:"0px","padding-right":"5px",cursor:"auto",font:'11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif',"z-index":"1001",background:"rgba(255,255,255,0.7)",clear:"both",display:"block","pointer-events":"auto"}).on("mousedown",function(evt){evt.stopPropagation()});return m_this.children().forEach(function(layer){var content=layer.attribution();content&&$("").addClass("geo-attribution-layer").css({"padding-left":"5px"}).html(content).appendTo($a)}),$a.appendTo(m_this.node()),m_this},m_origin={x:0,y:0},this.zoomRange(arg),m_zoom=fix_zoom(m_zoom),this.center($.extend({},arg.center||m_center),void 0),this.interactor(arg.interactor||geo.mapInteractor()),this.clock(arg.clock||geo.clock()),arg.autoResize&&$(window).resize(resizeSelf),m_this.geoOn([geo.event.layerAdd,geo.event.layerRemove],m_this.updateAttribution),this},geo.map.create=function(spec){"use strict";var map=geo.map(spec);return map?(spec.data=spec.data||[],spec.layers=spec.layers||[],spec.layers.forEach(function(l){l.data=l.data||spec.data,l.layer=geo.layer.create(map,l)}),map):(console.warn("Could not create map."),null)},inherit(geo.map,geo.sceneObject),geo.feature=function(arg){"use strict";if(!(this instanceof geo.feature))return new geo.feature(arg);geo.sceneObject.call(this),arg=arg||{};var m_this=this,s_exit=this._exit,m_selectionAPI=void 0===arg.selectionAPI?!1:arg.selectionAPI,m_style={},m_layer=void 0===arg.layer?null:arg.layer,m_gcs=arg.gcs,m_visible=void 0===arg.visible?!0:arg.visible,m_bin=void 0===arg.bin?0:arg.bin,m_renderer=void 0===arg.renderer?null:arg.renderer,m_dataTime=geo.timestamp(),m_buildTime=geo.timestamp(),m_updateTime=geo.timestamp(),m_selectedFeatures=[];return this._bindMouseHandlers=function(){m_selectionAPI&&(m_this._unbindMouseHandlers(),m_this.geoOn(geo.event.mousemove,m_this._handleMousemove),m_this.geoOn(geo.event.mouseclick,m_this._handleMouseclick),m_this.geoOn(geo.event.brushend,m_this._handleBrushend),m_this.geoOn(geo.event.brush,m_this._handleBrush))},this._unbindMouseHandlers=function(){m_this.geoOff(geo.event.mousemove,m_this._handleMousemove),m_this.geoOff(geo.event.mouseclick,m_this._handleMouseclick),m_this.geoOff(geo.event.brushend,m_this._handleBrushend),m_this.geoOff(geo.event.brush,m_this._handleBrush)},this.pointSearch=function(){return{index:[],found:[]}},this._handleMousemove=function(){var mouse=m_this.layer().map().interactor().mouse(),data=m_this.data(),over=m_this.pointSearch(mouse.geo),newFeatures=[],oldFeatures=[],lastTop=-1,top=-1;m_selectedFeatures.length&&(lastTop=m_selectedFeatures[m_selectedFeatures.length-1]),newFeatures=over.index.filter(function(i){return m_selectedFeatures.indexOf(i)<0}),oldFeatures=m_selectedFeatures.filter(function(i){return over.index.indexOf(i)<0}),geo.feature.eventID+=1,newFeatures.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseover,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===newFeatures.length-1},!0)}),geo.feature.eventID+=1,oldFeatures.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseout,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===oldFeatures.length-1},!0)}),geo.feature.eventID+=1,over.index.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mousemove,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===over.index.length-1},!0)}),m_selectedFeatures=over.index,m_selectedFeatures.length&&(top=m_selectedFeatures[m_selectedFeatures.length-1]),lastTop!==top&&(-1!==lastTop&&m_this.geoTrigger(geo.event.feature.mouseoff,{data:data[lastTop],index:lastTop,mouse:mouse},!0),-1!==top&&m_this.geoTrigger(geo.event.feature.mouseon,{data:data[top],index:top,mouse:mouse},!0))},this._handleMouseclick=function(){var mouse=m_this.layer().map().interactor().mouse(),data=m_this.data(),over=m_this.pointSearch(mouse.geo);geo.feature.eventID+=1,over.index.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.mouseclick,{data:data[i],index:i,mouse:mouse,eventID:geo.feature.eventID,top:idx===over.index.length-1},!0)})},this._handleBrush=function(brush){var idx=m_this.boxSearch(brush.gcs.lowerLeft,brush.gcs.upperRight),data=m_this.data();geo.feature.eventID+=1,idx.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.brush,{data:data[i],index:i,mouse:brush.mouse,brush:brush,eventID:geo.feature.eventID,top:idx===idx.length-1},!0)})},this._handleBrushend=function(brush){var idx=m_this.boxSearch(brush.gcs.lowerLeft,brush.gcs.upperRight),data=m_this.data();geo.feature.eventID+=1,idx.forEach(function(i,idx){m_this.geoTrigger(geo.event.feature.brushend,{data:data[i],index:i,mouse:brush.mouse,brush:brush,eventID:geo.feature.eventID,top:idx===idx.length-1},!0)})},this.style=function(arg1,arg2){return void 0===arg1?m_style:"string"==typeof arg1&&void 0===arg2?m_style[arg1]:void 0===arg2?(m_style=$.extend({},m_style,arg1),m_this.modified(),m_this):(m_style[arg1]=arg2,m_this.modified(),m_this)},this.style.get=function(key){var tmp,out;if(void 0===key){var k,all={};for(k in m_style)m_style.hasOwnProperty(k)&&(all[k]=m_this.style.get(k));return all}return key.toLowerCase().match(/color$/)?geo.util.isFunction(m_style[key])?(tmp=geo.util.ensureFunction(m_style[key]),out=function(){return geo.util.convertColor(tmp.apply(this,arguments))}):out=geo.util.ensureFunction(geo.util.convertColor(m_style[key])):out=geo.util.ensureFunction(m_style[key]),out},this.layer=function(){return m_layer},this.renderer=function(){return m_renderer},this.gcs=function(val){return void 0===val?void 0===m_gcs&&m_renderer?m_renderer.layer().map().ingcs():m_gcs:(m_gcs=val,m_this.modified(),m_this)},this.featureGcsToDisplay=function(c){var map=m_renderer.layer().map();return c=map.gcsToWorld(c,map.ingcs()),c=map.worldToDisplay(c),m_renderer.baseToLocal&&(c=m_renderer.baseToLocal(c)),c},this.visible=function(val){return void 0===val?m_visible:(m_visible=val,m_this.modified(),m_visible?m_this._bindMouseHandlers():m_this._unbindMouseHandlers(),m_this)},this.bin=function(val){return void 0===val?m_bin:(m_bin=val,m_this.modified(),m_this)},this.dataTime=function(val){return void 0===val?m_dataTime:(m_dataTime=val,m_this.modified(),m_this)},this.buildTime=function(val){return void 0===val?m_buildTime:(m_buildTime=val,m_this.modified(),m_this)},this.updateTime=function(val){return void 0===val?m_updateTime:(m_updateTime=val,m_this.modified(),m_this)},this.data=function(data){return void 0===data?m_this.style("data")||[]:(m_this.style("data",data),m_this.dataTime().modified(),m_this.modified(),m_this)},this.selectionAPI=function(){return m_selectionAPI},this._init=function(arg){if(!m_layer)throw"Feature requires a valid layer";m_style=$.extend({},{opacity:1},void 0===arg.style?{}:arg.style),m_this._bindMouseHandlers()},this._build=function(){},this._update=function(){},this._exit=function(){m_this._unbindMouseHandlers(),m_selectedFeatures=[],m_style={},arg={},s_exit()},this._init(arg),this},geo.feature.eventID=0,geo.feature.create=function(layer,spec){"use strict";var type=spec.type;if(!(layer instanceof geo.layer))return console.warn("Invalid layer"),null;if("object"!=typeof spec)return console.warn("Invalid spec"),null;var feature=layer.createFeature(type);return feature?(spec=spec||{},spec.data=spec.data||[],feature.style(spec)):(console.warn("Could not create feature type '"+type+"'"),null)},inherit(geo.feature,geo.sceneObject),geo.pointFeature=function(arg){"use strict";if(!(this instanceof geo.pointFeature))return new geo.pointFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init,m_rangeTree=null,m_rangeTreeTime=geo.timestamp(),s_data=this.data,m_maxRadius=0,m_clustering=arg.clustering,m_clusterTree=null,m_allData=[],m_lastZoom=null,m_ignoreData=!1;return this.clustering=function(val){return void 0===val?m_clustering:(m_clustering&&!val?(m_clusterTree=null,m_clustering=!1,s_data(m_allData),m_allData=null):!m_clustering&&val&&(m_clustering=!0,m_this._clusterData()),m_this)},this._clusterData=function(){if(m_clustering){var opts=m_clustering===!0?{radius:.01}:m_clustering,position=m_this.position();m_clusterTree=new geo.util.ClusterGroup(opts,m_this.layer().width(),m_this.layer().height()),m_allData.forEach(function(d,i){var pt=geo.util.normalizeCoordinates(position(d,i));pt.index=i,m_clusterTree.addPoint(pt)}),m_lastZoom=null,m_this._handleZoom(m_this.layer().map().zoom())}},this._handleZoom=function(zoom){var z=Math.floor(zoom);if(m_clustering&&z!==m_lastZoom){m_lastZoom=z;var data=m_clusterTree.points(z).map(function(d){return m_allData[d.index]});m_clusterTree.clusters(z).forEach(function(d){d.__cluster=!0,d.__data=[],d.obj.each(function(e){d.__data.push(m_allData[e.index])}),data.push(d)}),m_ignoreData=!0,m_this.data(data),m_this.layer().map().draw()}},this.position=function(val){return void 0===val?m_this.style("position"):(val=geo.util.ensureFunction(val),m_this.style("position",function(d,i){return d.__cluster?d:val(d,i)}),m_this.dataTime().modified(),m_this.modified(),m_this)},this._updateRangeTree=function(){if(!(m_rangeTreeTime.getMTime()>=m_this.dataTime().getMTime())){var pts,position,radius=m_this.style.get("radius"),stroke=m_this.style.get("stroke"),strokeWidth=m_this.style.get("strokeWidth");position=m_this.position(),m_maxRadius=0,pts=m_this.data().map(function(d,i){var pt=position(d);return pt.idx=i,m_maxRadius=Math.max(m_maxRadius,radius(d,i)+(stroke(d,i)?strokeWidth(d,i):0)),pt}),m_rangeTree=new geo.util.RangeTree(pts),m_rangeTreeTime.modified()}},this.pointSearch=function(p){var min,max,data,box,map,pt,idx=[],found=[],ifound=[],stroke=m_this.style.get("stroke"),strokeWidth=m_this.style.get("strokeWidth"),radius=m_this.style.get("radius");return m_this.selectionAPI()?(data=m_this.data(),data&&data.length?(map=m_this.layer().map(),pt=map.gcsToDisplay(p),min=map.displayToGcs({x:pt.x-m_maxRadius,y:pt.y+m_maxRadius}),max=map.displayToGcs({x:pt.x+m_maxRadius,y:pt.y-m_maxRadius}),box=new geo.util.Box(geo.util.vect(min.x,min.y),geo.util.vect(max.x,max.y)),m_this._updateRangeTree(),m_rangeTree.search(box).forEach(function(q){idx.push(q.idx)}),idx.forEach(function(i){var dx,dy,rad,d=data[i],p=m_this.position()(d,i);rad=radius(data[i],i),rad+=stroke(data[i],i)?strokeWidth(data[i],i):0,p=map.gcsToDisplay(p),dx=p.x-pt.x,dy=p.y-pt.y,Math.sqrt(dx*dx+dy*dy)<=rad&&(found.push(d),ifound.push(i))}),{data:found,index:ifound}):{found:[],index:[]}):[]},this.boxSearch=function(lowerLeft,upperRight){var pos=m_this.position(),idx=[];return m_this.data().forEach(function(d,i){var p=pos(d);p.x>=lowerLeft.x&&p.x<=upperRight.x&&p.y>=lowerLeft.y&&p.y<=upperRight.y&&idx.push(i)}),idx},this.data=function(data){return void 0===data?s_data():(m_clustering&&!m_ignoreData?(m_allData=data,m_this._clusterData()):s_data(data),m_ignoreData=!1,m_this)},this._boundingBox=function(d){var pt,radius;return pt=m_this.position()(d),pt=m_this.layer().map().gcsToDisplay(pt),radius=m_this.style().radius(d),{min:{x:pt.x-radius,y:pt.y-radius},max:{x:pt.x+radius,y:pt.y+radius}}},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{radius:5,stroke:!0,strokeColor:{r:.851,g:.604,b:0},strokeWidth:1.25,strokeOpacity:1,fillColor:{r:1,g:.839,b:.439},fill:!0,fillOpacity:.8,sprites:!1,sprites_image:null,position:function(d){return d}},void 0===arg.style?{}:arg.style);void 0!==arg.position&&(defaultStyle.position=arg.position),m_this.style(defaultStyle),m_this.dataTime().modified(),m_this.geoOn(geo.event.zoom,function(evt){m_this._handleZoom(evt.zoomLevel)})},m_this},geo.event.pointFeature=$.extend({},geo.event.feature),geo.pointFeature.create=function(layer,renderer,spec){"use strict";return spec.type="point",geo.feature.create(layer,spec)},inherit(geo.pointFeature,geo.feature),geo.lineFeature=function(arg){"use strict";if(!(this instanceof geo.lineFeature))return new geo.lineFeature(arg);arg=arg||{},geo.feature.call(this,arg);
+var m_this=this,s_init=this._init;return this.line=function(val){return void 0===val?m_this.style("line"):(m_this.style("line",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.position=function(val){return void 0===val?m_this.style("position"):(m_this.style("position",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.pointSearch=function(p){function lineDist2(q,u,v){var t,l2=dist2(u,v);return 1>l2?dist2(q,u):(t=((q.x-u.x)*(v.x-u.x)+(q.y-u.y)*(v.y-u.y))/l2,0>t?dist2(q,u):t>1?dist2(q,v):dist2(q,{x:u.x+t*(v.x-u.x),y:u.y+t*(v.y-u.y)}))}function dist2(u,v){var dx=u.x-v.x,dy=u.y-v.y;return dx*dx+dy*dy}var data,pt,map,line,width,pos,indices=[],found=[];return data=m_this.data(),data&&data.length?(map=m_this.layer().map(),line=m_this.line(),width=m_this.style.get("strokeWidth"),pos=m_this.position(),pt=m_this.featureGcsToDisplay(p),data.forEach(function(d,index){var last=null;try{line(d,index).forEach(function(current,j){var p=pos(current,j,d,index),s=m_this.featureGcsToDisplay(p),r=Math.ceil(width(p,j,d,index)/2)+2;if(r*=r,last&&lineDist2(pt,s,last)<=r)throw"found";last=s})}catch(err){if("found"!==err)throw err;found.push(d),indices.push(index)}}),{data:found,index:indices}):{found:[],index:[]}},this.boxSearch=function(lowerLeft,upperRight,opts){var pos=m_this.position(),idx=[],line=m_this.line();if(opts=opts||{},opts.partial=opts.partial||!1,opts.partial)throw"Unimplemented query method.";return m_this.data().forEach(function(d,i){var inside=!0;line(d,i).forEach(function(e,j){if(inside){var p=pos(e,j,d,i);p.x>=lowerLeft.x&&p.x<=upperRight.x&&p.y>=lowerLeft.y&&p.y<=upperRight.y||(inside=!1)}}),inside&&idx.push(i)}),idx},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeWidth:1,strokeColor:{r:1,g:.8431372549,b:0},strokeStyle:"solid",strokeOpacity:1,line:function(d){return d},position:function(d){return d}},void 0===arg.style?{}:arg.style);void 0!==arg.line&&(defaultStyle.line=arg.line),void 0!==arg.position&&(defaultStyle.position=arg.position),m_this.style(defaultStyle),m_this.dataTime().modified()},this._init(arg),this},geo.lineFeature.create=function(layer,spec){"use strict";return spec.type="line",geo.feature.create(layer,spec)},inherit(geo.lineFeature,geo.feature),geo.pathFeature=function(arg){"use strict";if(!(this instanceof geo.pathFeature))return new geo.pathFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,m_position=void 0===arg.position?[]:arg.position,s_init=this._init;return this.position=function(val){return void 0===val?m_position:(m_position=val,m_this.dataTime().modified(),m_this.modified(),m_this)},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeWidth:function(){return 1},strokeColor:function(){return{r:1,g:1,b:1}}},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_position&&m_this.dataTime().modified()},this._init(arg),this},inherit(geo.pathFeature,geo.feature),geo.polygonFeature=function(arg){"use strict";function getCoordinates(){var posFunc=m_this.position(),polyFunc=m_this.polygon();m_coordinates=m_this.data().map(function(d,i){var outer,inner,poly=polyFunc(d);return outer=(poly.outer||[]).map(function(d0,j){return posFunc.call(m_this,d0,j,d,i)}),inner=(poly.inner||[]).map(function(hole){return(hole||[]).map(function(d0,k){return posFunc.call(m_this,d0,k,d,i)})}),{outer:outer,inner:inner}})}if(!(this instanceof geo.polygonFeature))return new geo.polygonFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_position,m_polygon,m_this=this,s_init=this._init,s_data=this.data,m_coordinates={outer:[],inner:[]};return m_polygon=void 0===arg.polygon?function(d){return d}:arg.polygon,m_position=void 0===arg.position?function(d){return d}:arg.position,this.data=function(arg){var ret=s_data(arg);return void 0!==arg&&getCoordinates(),ret},this.polygon=function(val){return void 0===val?m_polygon:(m_polygon=val,m_this.dataTime().modified(),m_this.modified(),getCoordinates(),m_this)},this.position=function(val){return void 0===val?m_position:(m_position=val,m_this.dataTime().modified(),m_this.modified(),getCoordinates(),m_this)},this.pointSearch=function(coordinate){var found=[],indices=[],data=m_this.data();return m_coordinates.forEach(function(coord,i){var inside=geo.util.pointInPolygon(coordinate,coord.outer,coord.inner);inside&&(indices.push(i),found.push(data[i]))}),{index:indices,found:found}},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{fillColor:{r:0,g:.5,b:.5},fillOpacity:1},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_position&&m_this.dataTime().modified()},this._init(arg),this},inherit(geo.polygonFeature,geo.feature),geo.planeFeature=function(arg){"use strict";if(!(this instanceof geo.planeFeature))return new geo.planeFeature(arg);arg=arg||{},arg.ul=void 0===arg.ul?[0,1,0]:arg.ul,arg.lr=void 0===arg.lr?[1,0,0]:arg.lr,arg.depth=void 0===arg.depth?0:arg.depth,geo.polygonFeature.call(this,arg);var m_this=this,m_origin=[arg.ul.x,arg.lr.y,arg.depth],m_upperLeft=[arg.ul.x,arg.ul.y,arg.depth],m_lowerRight=[arg.lr.x,arg.lr.y,arg.depth],m_defaultDepth=arg.depth,m_drawOnAsyncResourceLoad=void 0===arg.drawOnAsyncResourceLoad?!0:!1,s_init=this._init;return this.origin=function(val){if(void 0===val)return m_origin;if(val instanceof Array){if(val.length>3||val.length<2)throw"Origin point requires point in 2 or 3 dimension";m_origin=val.slice(0),2===m_origin.length&&(m_origin[2]=m_defaultDepth)}return m_this.dataTime().modified(),m_this.modified(),m_this},this.upperLeft=function(val){if(void 0===val)return m_upperLeft;if(val instanceof Array){if(val.length>3||val.length<2)throw"Upper left point requires point in 2 or 3 dimension";m_upperLeft=val.slice(0),2===m_upperLeft.length&&(m_upperLeft[2]=m_defaultDepth)}return m_this.dataTime().modified(),m_this.modified(),m_this},this.lowerRight=function(val){if(void 0===val)return m_lowerRight;if(val instanceof Array){if(val.length>3||val.length<2)throw"Lower right point requires point in 2 or 3 dimension";m_lowerRight=val.slice(0),2===m_lowerRight.length&&(m_lowerRight[2]=m_defaultDepth),m_this.dataTime().modified()}return m_this.dataTime().modified(),m_this.modified(),m_this},this.drawOnAsyncResourceLoad=function(val){return void 0===val?m_drawOnAsyncResourceLoad:(m_drawOnAsyncResourceLoad=val,m_this)},this._init=function(arg){var style=null;s_init.call(m_this,arg),style=m_this.style(),void 0===style.image&&(style.image=null),m_this.style(style)},this._init(arg),this},inherit(geo.planeFeature,geo.polygonFeature),geo.vectorFeature=function(arg){"use strict";if(!(this instanceof geo.vectorFeature))return new geo.vectorFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init,s_style=this.style;this.origin=function(val){return void 0===val?s_style("origin"):(s_style("origin",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this.delta=function(val){return void 0===val?s_style("delta"):(s_style("delta",val),m_this.dataTime().modified(),m_this.modified(),m_this)},this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend({},{strokeColor:"black",strokeWidth:2,strokeOpacity:1,origin:{x:0,y:0,z:0},delta:function(d){return d},scale:null},void 0===arg.style?{}:arg.style);void 0!==arg.origin&&(defaultStyle.origin=arg.origin),m_this.style(defaultStyle),m_this.dataTime().modified()}},inherit(geo.vectorFeature,geo.feature),geo.geomFeature=function(arg){"use strict";return this instanceof geo.geomFeature?(arg=arg||{},geo.feature.call(this,arg),arg.style=void 0===arg.style?$.extend({},{color:[1,1,1],point_sprites:!1,point_sprites_image:null},arg.style):arg.style,this.style(arg.style),this):new geo.geomFeature(arg)},inherit(geo.geomFeature,geo.feature),geo.graphFeature=function(arg){"use strict";if(!(this instanceof geo.graphFeature))return new geo.graphFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_draw=this.draw,s_style=this.style,m_nodes=null,m_points=null,m_children=function(d){return d.children},m_links=[],s_init=this._init,s_exit=this._exit;return this._init=function(arg){s_init.call(m_this,arg);var defaultStyle=$.extend(!0,{},{nodes:{radius:5,fill:!0,fillColor:{r:1,g:0,b:0},strokeColor:{r:0,g:0,b:0}},links:{strokeColor:{r:0,g:0,b:0}},linkType:"path"},void 0===arg.style?{}:arg.style);m_this.style(defaultStyle),m_this.nodes(function(d){return d})},this._build=function(){m_this.children().forEach(function(child){child._build()})},this._update=function(){m_this.children().forEach(function(child){child._update()})},this._exit=function(){return m_this.data([]),m_links.forEach(function(link){link._exit(),m_this.removeChild(link)}),m_links=[],m_points._exit(),m_this.removeChild(m_points),s_exit(),m_this},this.style=function(arg,arg2){var out=s_style.call(m_this,arg,arg2);return out!==m_this?out:(m_points.style(arg.nodes),m_links.forEach(function(l){l.style(arg.links)}),m_this)},this.links=function(arg){return void 0===arg?m_children:(m_children=geo.util.ensureFunction(arg),m_this)},this.nodes=function(val){return void 0===val?m_nodes:(m_nodes=val,m_this.modified(),m_this)},this.nodeFeature=function(){return m_points},this.linkFeatures=function(){return m_links},this.draw=function(){var style,layer=m_this.layer(),data=m_this.data(),nLinks=0;return style=m_this.style(),m_points.data(data),m_points.style(style.nodes),data.forEach(function(source){(source.children||[]).forEach(function(target){var link;nLinks+=1,m_links.lengthdata.length&&(gridH=Math.floor(data.length)/gridW),usePos=null===x0||void 0===x0||null===y0||void 0===y0||!dx||!dy,!usePos&&result.wrapLongitude&&(-180>x0||x0>180||-180>x0+dx*(gridW-1)||x0+dx*(gridW-1)>180)&&dx>-180&&180>dx){for(calcX=[],i=0;gridW>i;i+=1){for(x=x0+i*dx;-180>x;)x+=360;for(;x>180;)x-=360;i&&Math.abs(x-calcX[calcX.length-1])>180&&(x>calcX[calcX.length-1]?(calcX.push(x-360),calcX.push(calcX[calcX.length-2]+360)):(calcX.push(x+360),calcX.push(calcX[calcX.length-2]-360)),skipColumn=i),calcX.push(x)}if(gridW+=2,Math.abs(Math.abs(gridWorig*dx)-360)<.01){for(gridW+=1,x=x0+gridWorig*dx;-180>x;)x+=360;for(;x>180;)x-=360;calcX.push(x)}}for(numPts=gridW*gridH,i=0;numPts>i;i+=1)void 0===skipColumn?val=parseFloat(valueFunc(data[i])):(j=Math.floor(i/gridW),origI=i-j*gridW,origI+=origI>skipColumn?-2:0,origI>=gridWorig&&(origI-=gridWorig),origI+=j*gridWorig,val=parseFloat(valueFunc(data[origI]))),values[i]=isNaN(val)?null:val,null!==values[i]&&(idxMap[i]=usedPts,usedPts+=1,void 0===minval&&(minval=maxval=values[i]),values[i]maxval&&(maxval=values[i]));if(!usedPts)return result;if($.isNumeric(result.minValue)||(result.minValue=minval),$.isNumeric(result.maxValue)||(result.maxValue=maxval),rangeValues&&rangeValues.length===result.colorMap.length+1||(rangeValues=null),rangeValues)for(k=1;krangeValues[k+1]){rangeValues=null;break}for(rangeValues&&(result.minValue=rangeValues[0],result.maxValue=rangeValues[rangeValues.length-1]),range=result.maxValue-result.minValue,range||(result.colorMap=result.colorMap.slice(0,1),range=1,rangeValues=null),result.rangeValues=rangeValues,result.factor=result.colorMap.length/range,j=idx=0;gridH-1>j;j+=1,idx+=1)for(i=0;gridW-1>i;i+=1,idx+=1)null!==values[idx]&&null!==values[idx+1]&&null!==values[idx+gridW]&&null!==values[idx+gridW+1]&&i!==skipColumn&&(result.elements.push(idxMap[idx]),result.elements.push(idxMap[idx+1]),result.elements.push(idxMap[idx+gridW]),result.elements.push(idxMap[idx+gridW+1]),result.elements.push(idxMap[idx+gridW]),result.elements.push(idxMap[idx+1]));for(result.pos=new Array(3*usedPts),result.value=new Array(usedPts),result.opacity=new Array(usedPts),j=i=i3=0;numPts>j;j+=1)if(val=values[j],null!==val){if(item=data[j],usePos?(posVal=posFunc(item),result.pos[i3]=posVal.x,result.pos[i3+1]=posVal.y,result.pos[i3+2]=posVal.z||0):(void 0===skipColumn?result.pos[i3]=x0+dx*(j%gridW):result.pos[i3]=calcX[j%gridW],result.pos[i3+1]=y0+dy*Math.floor(j/gridW),result.pos[i3+2]=0),result.opacity[i]=opacityFunc(item),rangeValues&&val>=result.minValue&&val<=result.maxValue){for(k=1;kOpenStreetMap contributors'}),inherit(geo.osmLayer,geo.tileLayer),geo.registerLayer("osm",geo.osmLayer)}(),geo.domRenderer=function(arg){"use strict";if(!(this instanceof geo.domRenderer))return new geo.domRenderer(arg);geo.renderer.call(this,arg),arg=arg||{};var m_this=this;return this.api=function(){return"dom"},this._init=function(){var layer=m_this.layer().node();!m_this.canvas()&&layer&&layer.length&&m_this.canvas(layer[0])},this._init(arg),this},inherit(geo.domRenderer,geo.renderer),geo.registerRenderer("dom",geo.domRenderer),geo.choroplethFeature=function(arg){"use strict";if(!(this instanceof geo.choroplethFeature))return new geo.choroplethFeature(arg);arg=arg||{},geo.feature.call(this,arg);var m_this=this,s_init=this._init,m_choropleth=$.extend({},{colorRange:[{r:.07514311,g:.468049805,b:1},{r:.468487184,g:.588057293,b:1},{r:.656658579,g:.707001303,b:1},{r:.821573924,g:.837809045,b:1},{r:.943467973,g:.943498599,b:.943398095},{r:1,g:.788626485,b:.750707739},{r:1,g:.6289553,b:.568237474},{r:1,g:.472800903,b:.404551679},{r:.916482116,g:.236630659,b:.209939162}],scale:d3.scale.quantize(),accessors:{geoId:function(geoFeature){return geoFeature.properties.GEO_ID},scalarId:function(scalarElement){return scalarElement.id},scalarValue:function(scalarElement){return scalarElement.value}}},arg.choropleth);return this.scalar=function(data,aggregator){var scalarId,scalarValue;return void 0===data?m_this.choropleth.get("scalar")():(scalarId=m_this.choropleth.get("accessors")().scalarId,scalarValue=m_this.choropleth.get("accessors")().scalarValue,m_choropleth.scalar=data,m_choropleth.scalarAggregator=aggregator||d3.mean,m_choropleth.scalar._dictionary=data.reduce(function(accumeDictionary,scalarElement){var id,value;return id=scalarId(scalarElement),value=scalarValue(scalarElement),accumeDictionary[id]=accumeDictionary[id]?accumeDictionary[id].push(value):[value],accumeDictionary},{}),m_this.dataTime().modified(),m_this)},this.choropleth=function(arg1,arg2){var choropleth;return void 0===arg1?m_choropleth:"string"==typeof arg1&&void 0===arg2?m_choropleth[arg1]:(void 0===arg2?(choropleth=$.extend({},m_choropleth,arg1),m_choropleth=choropleth):m_choropleth[arg1]=arg2,m_this.modified(),m_this)},this.choropleth.get=function(key){var k,all={};if(void 0===key){for(k in m_choropleth)m_choropleth.hasOwnProperty(k)&&(all[k]=m_this.choropleth.get(k));return all}return geo.util.ensureFunction(m_choropleth[key])},this._addPolygonFeature=function(feature,fillColor){var newFeature=m_this.layer().createFeature("polygon",{});return"Polygon"===feature.geometry.type?newFeature.data([{type:"Polygon",coordinates:feature.geometry.coordinates}]):"MultiPolygon"===feature.geometry.type&&newFeature.data(feature.geometry.coordinates.map(function(coordinateMap){return{type:"Polygon",coordinates:coordinateMap}})),newFeature.polygon(function(d){return{outer:d.coordinates[0],inner:d.coordinates[1]}}).position(function(d){return{x:d[0],y:d[1]}}).style({fillColor:fillColor}),newFeature},this._featureToPolygons=function(feature,fillValue){return m_this._addPolygonFeature(feature,fillValue)},this._generateScale=function(valueAccessor){var extent=d3.extent(m_this.scalar(),valueAccessor||void 0);return m_this.choropleth().scale.domain(extent).range(m_this.choropleth().colorRange),m_this},this.createChoropleth=function(){var choropleth=m_this.choropleth,data=m_this.data(),scalars=m_this.scalar(),valueFunc=choropleth.get("accessors")().scalarValue,getFeatureId=choropleth.get("accessors")().geoId;return m_this._generateScale(valueFunc),data.map(function(feature){var id=getFeatureId(feature),valueArray=scalars._dictionary[id],accumulatedScalarValue=choropleth().scalarAggregator(valueArray),fillColor=m_this.choropleth().scale(accumulatedScalarValue);return m_this._featureToPolygons(feature,fillColor)})},this._init=function(arg){s_init.call(m_this,arg),m_choropleth&&m_this.dataTime().modified()},this._init(arg),this},inherit(geo.choroplethFeature,geo.feature),geo.gl={},geo.gl.lineFeature=function(arg){"use strict";function createVertexShader(){var vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute vec3 prev;","attribute vec3 next;","attribute float offset;","attribute vec3 strokeColor;","attribute float strokeOpacity;","attribute float strokeWidth;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform float pixelWidth;","uniform float aspect;","varying vec3 strokeColorVar;","varying float strokeWidthVar;","varying float strokeOpacityVar;","void main(void)","{"," if (strokeOpacity < 0.0) {"," gl_Position = vec4(2, 2, 0, 1);"," return;"," }"," const float PI = 3.14159265358979323846264;"," vec4 worldPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1);"," if (worldPos.w != 0.0) {"," worldPos = worldPos/worldPos.w;"," }"," vec4 worldNext = projectionMatrix * modelViewMatrix * vec4(next.xyz, 1);"," if (worldNext.w != 0.0) {"," worldNext = worldNext/worldNext.w;"," }"," vec4 worldPrev = projectionMatrix* modelViewMatrix * vec4(prev.xyz, 1);"," if (worldPrev.w != 0.0) {"," worldPrev = worldPrev/worldPrev.w;"," }"," strokeColorVar = strokeColor;"," strokeWidthVar = strokeWidth;"," strokeOpacityVar = strokeOpacity;"," vec2 deltaNext = worldNext.xy - worldPos.xy;"," vec2 deltaPrev = worldPos.xy - worldPrev.xy;"," float angleNext = 0.0, anglePrev = 0.0;"," if (deltaNext.xy != vec2(0.0, 0.0))"," angleNext = atan(deltaNext.y / aspect, deltaNext.x);"," if (deltaPrev.xy == vec2(0.0, 0.0)) anglePrev = angleNext;"," else anglePrev = atan(deltaPrev.y / aspect, deltaPrev.x);"," if (deltaNext.xy == vec2(0.0, 0.0)) angleNext = anglePrev;"," float angle = (anglePrev + angleNext) / 2.0;"," float cosAngle = cos(anglePrev - angle);"," if (cosAngle < 0.1) { cosAngle = sign(cosAngle) * 1.0; angle = 0.0; }"," float distance = (offset * strokeWidth * pixelWidth) /"," cosAngle;"," worldPos.x += distance * sin(angle);"," worldPos.y -= distance * cos(angle) * aspect;"," gl_Position = worldPos;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","varying vec3 strokeColorVar;","varying float strokeWidthVar;","varying float strokeOpacityVar;","void main () {"," gl_FragColor = vec4 (strokeColorVar, strokeOpacityVar);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function createGLLines(){var i,j,k,v,len,lineItem,lineItemData,vertTemp,pos,posIdx3,posBuf,nextBuf,prevBuf,offsetBuf,indicesBuf,strokeWidthBuf,strokeColorBuf,strokeOpacityBuf,dest,dest3,data=m_this.data(),numSegments=0,vert=[{},{}],position=[],posFunc=m_this.position(),strkWidthFunc=m_this.style.get("strokeWidth"),strkColorFunc=m_this.style.get("strokeColor"),strkOpacityFunc=m_this.style.get("strokeOpacity"),order=m_this.featureVertices(),geom=m_mapper.geometryData();for(i=0;i=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_pixelWidthUnif.set(1/m_this.renderer().width()),m_aspectUniform.set(m_this.renderer().width()/m_this.renderer().height()),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.lineFeature,geo.lineFeature),geo.registerFeature("vgl","line",geo.gl.lineFeature),geo.gl.pointFeature=function(arg){"use strict";function createVertexShader(){var shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function pointPolygon(x,y,w,h){var verts;switch(m_primitiveShape){case"triangle":verts=[x,y-2*h,x-w*Math.sqrt(3),y+h,x+w*Math.sqrt(3),y+h];break;case"sprite":verts=[x,y];break;default:verts=[x-w,y+h,x-w,y-h,x+w,y+h,x-w,y-h,x+w,y-h,x+w,y+h]}return verts}function createGLPoints(){var i,j,posBuf,posVal,posFunc,unitBuf,indices,radius,radiusVal,radFunc,stroke,strokeVal,strokeFunc,strokeWidth,strokeWidthVal,strokeWidthFunc,strokeOpacity,strokeOpacityVal,strokeOpactityFunc,strokeColor,strokeColorVal,strokeColorFunc,fill,fillVal,fillFunc,fillOpacity,fillOpacityVal,fillOpacityFunc,fillColor,fillColorVal,fillColorFunc,item,ivpf,ivpf3,iunit,i3,numPts=m_this.data().length,unit=pointPolygon(0,0,1,1),position=new Array(3*numPts),vpf=m_this.verticesPerFeature(),data=m_this.data(),geom=m_mapper.geometryData();
+for(posFunc=m_this.position(),radFunc=m_this.style.get("radius"),strokeFunc=m_this.style.get("stroke"),strokeWidthFunc=m_this.style.get("strokeWidth"),strokeOpactityFunc=m_this.style.get("strokeOpacity"),strokeColorFunc=m_this.style.get("strokeColor"),fillFunc=m_this.style.get("fill"),fillOpacityFunc=m_this.style.get("fillOpacity"),fillColorFunc=m_this.style.get("fillColor"),i=i3=0;numPts>i;i+=1,i3+=3)posVal=posFunc(data[i]),position[i3]=posVal.x,position[i3+1]=posVal.y,position[i3+2]=posVal.z||0;for(position=geo.transform.transformCoordinates(m_this.gcs(),m_this.layer().map().gcs(),position,3),posBuf=getBuffer(geom,"pos",vpf*numPts*3),"sprite"!==m_primitiveShape&&(unitBuf=getBuffer(geom,"unit",vpf*numPts*2)),radius=getBuffer(geom,"rad",vpf*numPts*1),stroke=getBuffer(geom,"stroke",vpf*numPts*1),strokeWidth=getBuffer(geom,"strokeWidth",vpf*numPts*1),strokeOpacity=getBuffer(geom,"strokeOpacity",vpf*numPts*1),strokeColor=getBuffer(geom,"strokeColor",vpf*numPts*3),fill=getBuffer(geom,"fill",vpf*numPts*1),fillOpacity=getBuffer(geom,"fillOpacity",vpf*numPts*1),fillColor=getBuffer(geom,"fillColor",vpf*numPts*3),indices=geom.primitive(0).indices(),indices instanceof Uint16Array&&indices.length===vpf*numPts||(indices=new Uint16Array(vpf*numPts),geom.primitive(0).setIndices(indices)),i=ivpf=ivpf3=iunit=i3=0;numPts>i;i+=1,i3+=3){if(item=data[i],"sprite"!==m_primitiveShape)for(j=0;jj;j+=1,ivpf+=1,ivpf3+=3)posBuf[ivpf3]=position[i3],posBuf[ivpf3+1]=position[i3+1],posBuf[ivpf3+2]=position[i3+2],radius[ivpf]=radiusVal,stroke[ivpf]=strokeVal,strokeWidth[ivpf]=strokeWidthVal,strokeOpacity[ivpf]=strokeOpacityVal,strokeColor[ivpf3]=strokeColorVal.r,strokeColor[ivpf3+1]=strokeColorVal.g,strokeColor[ivpf3+2]=strokeColorVal.b,fill[ivpf]=fillVal,fillOpacity[ivpf]=fillOpacityVal,fillColor[ivpf3]=fillColorVal.r,fillColor[ivpf3+1]=fillColorVal.g,fillColor[ivpf3+2]=fillColorVal.b}geom.boundsDirty(!0),m_mapper.modified(),m_mapper.boundsDirtyTimestamp().modified()}function getBuffer(geom,srcName,len){var data,src=geom.sourceByName(srcName);return data=src.data(),data instanceof Float32Array&&data.length===len?data:(data=new Float32Array(len),src.setData(data),data)}if(!(this instanceof geo.gl.pointFeature))return new geo.gl.pointFeature(arg);arg=arg||{},geo.pointFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_actor=null,m_mapper=null,m_pixelWidthUniform=null,m_aspectUniform=null,m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,m_primitiveShape="sprite",s_init=this._init,s_update=this._update,vertexShaderSource=null,fragmentShaderSource=null;return("triangle"===arg.primitiveShape||"square"===arg.primitiveShape||"sprite"===arg.primitiveShape)&&(m_primitiveShape=arg.primitiveShape),vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute float rad;","attribute vec3 fillColor;","attribute vec3 strokeColor;","attribute float fillOpacity;","attribute float strokeWidth;","attribute float strokeOpacity;","attribute float fill;","attribute float stroke;","uniform float pixelWidth;","uniform float aspect;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying vec4 fillColorVar;","varying vec4 strokeColorVar;","varying float radiusVar;","varying float strokeWidthVar;","varying float fillVar;","varying float strokeVar;"],"sprite"!==m_primitiveShape&&(vertexShaderSource=vertexShaderSource.concat(["attribute vec2 unit;","varying vec3 unitVar;"])),vertexShaderSource.push.apply(vertexShaderSource,["void main(void)","{"," strokeWidthVar = strokeWidth;"," // No stroke or fill implies nothing to draw"," if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) {"," strokeVar = 0.0;"," strokeWidthVar = 0.0;"," }"," else"," strokeVar = 1.0;"," if (fill < 1.0 || rad <= 0.0 || fillOpacity <= 0.0)"," fillVar = 0.0;"," else"," fillVar = 1.0;"," if (fillVar == 0.0 && strokeVar == 0.0) {"," gl_Position = vec4(2, 2, 0, 1);"," return;"," }"," fillColorVar = vec4 (fillColor, fillOpacity);"," strokeColorVar = vec4 (strokeColor, strokeOpacity);"," radiusVar = rad;"]),"sprite"===m_primitiveShape?vertexShaderSource.push.apply(vertexShaderSource,[" gl_Position = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;"," gl_PointSize = 2.0 * (rad + strokeWidthVar); ","}"]):vertexShaderSource.push.apply(vertexShaderSource,[" unitVar = vec3 (unit, 1.0);"," vec4 p = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw;"," if (p.w != 0.0) {"," p = p / p.w;"," }"," p += (rad + strokeWidthVar) * "," vec4 (unit.x * pixelWidth, unit.y * pixelWidth * aspect, 0.0, 1.0);"," gl_Position = vec4(p.xyz, 1.0);","}"]),vertexShaderSource=vertexShaderSource.join("\n"),fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","uniform float aspect;","varying vec4 fillColorVar;","varying vec4 strokeColorVar;","varying float radiusVar;","varying float strokeWidthVar;","varying float fillVar;","varying float strokeVar;"],"sprite"!==m_primitiveShape&&fragmentShaderSource.push("varying vec3 unitVar;"),fragmentShaderSource.push.apply(fragmentShaderSource,["void main () {"," vec4 strokeColor, fillColor;"," float endStep;"," // No stroke or fill implies nothing to draw"," if (fillVar == 0.0 && strokeVar == 0.0)"," discard;"]),"sprite"===m_primitiveShape?fragmentShaderSource.push(" float rad = 2.0 * length (gl_PointCoord - vec2(0.5));"):fragmentShaderSource.push(" float rad = length (unitVar.xy);"),fragmentShaderSource.push.apply(fragmentShaderSource,[" if (rad > 1.0)"," discard;"," // If there is no stroke, the fill region should transition to nothing"," if (strokeVar == 0.0) {"," strokeColor = vec4 (fillColorVar.rgb, 0.0);"," endStep = 1.0;"," } else {"," strokeColor = strokeColorVar;"," endStep = radiusVar / (radiusVar + strokeWidthVar);"," }"," // Likewise, if there is no fill, the stroke should transition to nothing"," if (fillVar == 0.0)"," fillColor = vec4 (strokeColor.rgb, 0.0);"," else"," fillColor = fillColorVar;"," // Distance to antialias over"," float antialiasDist = 3.0 / (2.0 * radiusVar);"," if (rad < endStep) {"," float step = smoothstep (endStep - antialiasDist, endStep, rad);"," gl_FragColor = mix (fillColor, strokeColor, step);"," } else {"," float step = smoothstep (1.0 - antialiasDist, 1.0, rad);"," gl_FragColor = mix (strokeColor, vec4 (strokeColor.rgb, 0.0), step);"," }","}"]),fragmentShaderSource=fragmentShaderSource.join("\n"),this.actors=function(){return m_actor?[m_actor]:[]},this.verticesPerFeature=function(){var unit=pointPolygon(0,0,1,1);return unit.length/2},this._init=function(){var prog=vgl.shaderProgram(),vertexShader=createVertexShader(),fragmentShader=createFragmentShader(),posAttr=vgl.vertexAttribute("pos"),unitAttr=vgl.vertexAttribute("unit"),radAttr=vgl.vertexAttribute("rad"),strokeWidthAttr=vgl.vertexAttribute("strokeWidth"),fillColorAttr=vgl.vertexAttribute("fillColor"),fillAttr=vgl.vertexAttribute("fill"),strokeColorAttr=vgl.vertexAttribute("strokeColor"),strokeAttr=vgl.vertexAttribute("stroke"),fillOpacityAttr=vgl.vertexAttribute("fillOpacity"),strokeOpacityAttr=vgl.vertexAttribute("strokeOpacity"),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),mat=vgl.material(),blend=vgl.blend(),geom=vgl.geometryData(),sourcePositions=vgl.sourceDataP3fv({name:"pos"}),sourceUnits=vgl.sourceDataAnyfv(2,vgl.vertexAttributeKeysIndexed.One,{name:"unit"}),sourceRadius=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Two,{name:"rad"}),sourceStrokeWidth=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Three,{name:"strokeWidth"}),sourceFillColor=vgl.sourceDataAnyfv(3,vgl.vertexAttributeKeysIndexed.Four,{name:"fillColor"}),sourceFill=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Five,{name:"fill"}),sourceStrokeColor=vgl.sourceDataAnyfv(3,vgl.vertexAttributeKeysIndexed.Six,{name:"strokeColor"}),sourceStroke=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Seven,{name:"stroke"}),sourceAlpha=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Eight,{name:"fillOpacity"}),sourceStrokeOpacity=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Nine,{name:"strokeOpacity"}),primitive=new vgl.triangles;"sprite"===m_primitiveShape&&(primitive=new vgl.points),m_pixelWidthUniform=new vgl.floatUniform("pixelWidth",2/m_this.renderer().width()),m_aspectUniform=new vgl.floatUniform("aspect",m_this.renderer().width()/m_this.renderer().height()),s_init.call(m_this,arg),m_mapper=vgl.mapper({dynamicDraw:m_dynamicDraw}),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),"sprite"!==m_primitiveShape&&prog.addVertexAttribute(unitAttr,vgl.vertexAttributeKeysIndexed.One),prog.addVertexAttribute(radAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addVertexAttribute(strokeWidthAttr,vgl.vertexAttributeKeysIndexed.Three),prog.addVertexAttribute(fillColorAttr,vgl.vertexAttributeKeysIndexed.Four),prog.addVertexAttribute(fillAttr,vgl.vertexAttributeKeysIndexed.Five),prog.addVertexAttribute(strokeColorAttr,vgl.vertexAttributeKeysIndexed.Six),prog.addVertexAttribute(strokeAttr,vgl.vertexAttributeKeysIndexed.Seven),prog.addVertexAttribute(fillOpacityAttr,vgl.vertexAttributeKeysIndexed.Eight),prog.addVertexAttribute(strokeOpacityAttr,vgl.vertexAttributeKeysIndexed.Nine),prog.addUniform(m_pixelWidthUniform),prog.addUniform(m_aspectUniform),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),mat.addAttribute(prog),mat.addAttribute(blend),m_actor=vgl.actor(),m_actor.setMaterial(mat),m_actor.setMapper(m_mapper),geom.addSource(sourcePositions),geom.addSource(sourceUnits),geom.addSource(sourceRadius),geom.addSource(sourceStrokeWidth),geom.addSource(sourceFillColor),geom.addSource(sourceFill),geom.addSource(sourceStrokeColor),geom.addSource(sourceStroke),geom.addSource(sourceAlpha),geom.addSource(sourceStrokeOpacity),geom.addPrimitive(primitive),m_mapper.setGeometryData(geom)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLPoints(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.renderer().contextRenderer().render(),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()i;i+=1)buffers.write("pos",position[i],start+i,1),buffers.write("indices",[i],start+i,1),buffers.write("fillColor",fillColorNew[i],start+i,1),buffers.write("fillOpacity",[fillOpacityNew[i]],start+i,1);sourcePositions.pushBack(buffers.get("pos")),geom.addSource(sourcePositions),sourceFillColor.pushBack(buffers.get("fillColor")),geom.addSource(sourceFillColor),sourceFillOpacity.pushBack(buffers.get("fillOpacity")),geom.addSource(sourceFillOpacity),trianglePrimitive.setIndices(buffers.get("indices")),geom.addPrimitive(trianglePrimitive),m_mapper.setGeometryData(geom)}if(!(this instanceof geo.gl.polygonFeature))return new geo.gl.polygonFeature(arg);arg=arg||{},geo.polygonFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_actor=vgl.actor(),m_mapper=vgl.mapper(),m_material=vgl.material(),s_init=this._init,s_update=this._update;return this._init=function(arg){var blend=vgl.blend(),prog=vgl.shaderProgram(),posAttr=vgl.vertexAttribute("pos"),fillColorAttr=vgl.vertexAttribute("fillColor"),fillOpacityAttr=vgl.vertexAttribute("fillOpacity"),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),vertexShader=createVertexShader(),fragmentShader=createFragmentShader();s_init.call(m_this,arg),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(fillColorAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addVertexAttribute(fillOpacityAttr,vgl.vertexAttributeKeysIndexed.Three),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),m_material.addAttribute(prog),m_material.addAttribute(blend),m_actor.setMapper(m_mapper),m_actor.setMaterial(m_material)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLPolygons(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.polygonFeature,geo.polygonFeature),geo.registerFeature("vgl","polygon",geo.gl.polygonFeature),geo.gl.contourFeature=function(arg){"use strict";function createVertexShader(){var vertexShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","attribute vec3 pos;","attribute float value;","attribute float opacity;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","varying float valueVar;","varying float opacityVar;","void main(void)","{"," vec4 scrPos = projectionMatrix * modelViewMatrix * vec4(pos.xy, 0, 1);"," if (scrPos.w != 0.0) {"," scrPos = scrPos / scrPos.w;"," }"," valueVar = value;"," opacityVar = opacity;"," gl_Position = scrPos;","}"].join("\n"),shader=new vgl.shader(vgl.GL.VERTEX_SHADER);return shader.setShaderSource(vertexShaderSource),shader}function createFragmentShader(){var fragmentShaderSource=["#ifdef GL_ES"," precision highp float;","#endif","uniform vec4 minColor;","uniform vec4 maxColor;","uniform float steps;","uniform bool stepped;","uniform sampler2D sampler2d;","varying float valueVar;","varying float opacityVar;","void main () {"," vec4 clr;"," if (valueVar < 0.0) {"," clr = minColor;"," } else if (valueVar > steps) {"," clr = maxColor;"," } else {"," float step;"," if (stepped) {"," step = floor(valueVar) + 0.5;"," if (step > steps) {"," step = steps - 0.5;"," }"," } else {"," step = valueVar;"," }"," clr = texture2D(sampler2d, vec2(step / steps, 0.0));"," }"," gl_FragColor = vec4(clr.rgb, clr.a * opacityVar);","}"].join("\n"),shader=new vgl.shader(vgl.GL.FRAGMENT_SHADER);return shader.setShaderSource(fragmentShaderSource),shader}function createGLContours(){var i,i3,j,j3,posBuf,opacityBuf,valueBuf,indicesBuf,contour=m_this.createContours(),numPts=contour.elements.length,colorTable=[],geom=m_mapper.geometryData();for(m_minColorUniform.set([contour.minColor.r,contour.minColor.g,contour.minColor.b,contour.minColor.a]),m_maxColorUniform.set([contour.maxColor.r,contour.maxColor.g,contour.maxColor.b,contour.maxColor.a]),m_stepsUniform.set(contour.colorMap.length),m_steppedUniform.set(contour.stepped),i=0;ii;i+=1,i3+=3)j=contour.elements[i],j3=3*j,posBuf[i3]=contour.pos[j3],posBuf[i3+1]=contour.pos[j3+1],posBuf[i3+2]=contour.pos[j3+2],opacityBuf[i]=contour.opacity[j],valueBuf[i]=contour.value[j];indicesBuf=geom.primitive(0).indices(),indicesBuf instanceof Uint16Array&&indicesBuf.length===numPts||(indicesBuf=new Uint16Array(numPts),geom.primitive(0).setIndices(indicesBuf)),geom.boundsDirty(!0),m_mapper.modified(),m_mapper.boundsDirtyTimestamp().modified()}function getBuffer(geom,srcName,len){var data,src=geom.sourceByName(srcName);return data=src.data(),data instanceof Float32Array&&data.length===len?data:(data=new Float32Array(len),src.setData(data),data)}if(!(this instanceof geo.gl.contourFeature))return new geo.gl.contourFeature(arg);arg=arg||{},geo.contourFeature.call(this,arg);var m_this=this,s_exit=this._exit,m_textureUnit=7,m_actor=null,m_mapper=null,m_material=null,m_texture=null,m_minColorUniform=null,m_maxColorUniform=null,m_stepsUniform=null,m_steppedUniform=null,m_dynamicDraw=void 0===arg.dynamicDraw?!1:arg.dynamicDraw,s_init=this._init,s_update=this._update;return this._init=function(arg){var blend=vgl.blend(),prog=vgl.shaderProgram(),mat=vgl.material(),tex=vgl.lookupTable(),geom=vgl.geometryData(),modelViewUniform=new vgl.modelViewUniform("modelViewMatrix"),projectionUniform=new vgl.projectionUniform("projectionMatrix"),samplerUniform=new vgl.uniform(vgl.GL.INT,"sampler2d"),vertexShader=createVertexShader(),fragmentShader=createFragmentShader(),posAttr=vgl.vertexAttribute("pos"),valueAttr=vgl.vertexAttribute("value"),opacityAttr=vgl.vertexAttribute("opacity"),sourcePositions=vgl.sourceDataP3fv({name:"pos"}),sourceValues=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.One,{name:"value"}),sourceOpacity=vgl.sourceDataAnyfv(1,vgl.vertexAttributeKeysIndexed.Two,{name:"opacity"}),primitive=new vgl.triangles;s_init.call(m_this,arg),m_mapper=vgl.mapper({dynamicDraw:m_dynamicDraw}),prog.addVertexAttribute(posAttr,vgl.vertexAttributeKeys.Position),prog.addVertexAttribute(valueAttr,vgl.vertexAttributeKeysIndexed.One),prog.addVertexAttribute(opacityAttr,vgl.vertexAttributeKeysIndexed.Two),prog.addUniform(modelViewUniform),prog.addUniform(projectionUniform),m_minColorUniform=new vgl.uniform(vgl.GL.FLOAT_VEC4,"minColor"),prog.addUniform(m_minColorUniform),m_maxColorUniform=new vgl.uniform(vgl.GL.FLOAT_VEC4,"maxColor"),prog.addUniform(m_maxColorUniform),m_stepsUniform=new vgl.uniform(vgl.GL.FLOAT,"steps"),prog.addUniform(m_stepsUniform),m_steppedUniform=new vgl.uniform(vgl.GL.BOOL,"stepped"),prog.addUniform(m_steppedUniform),prog.addShader(fragmentShader),prog.addShader(vertexShader),prog.addUniform(samplerUniform),tex.setTextureUnit(m_textureUnit),samplerUniform.set(m_textureUnit),m_material=mat,m_material.addAttribute(prog),m_material.addAttribute(blend),m_texture=tex,m_material.addAttribute(m_texture),m_actor=vgl.actor(),m_actor.setMaterial(m_material),m_actor.setMapper(m_mapper),geom.addSource(sourcePositions),geom.addSource(sourceValues),geom.addSource(sourceOpacity),geom.addPrimitive(primitive),m_mapper.setGeometryData(geom)},this._build=function(){m_actor&&m_this.renderer().contextRenderer().removeActor(m_actor),createGLContours(),m_this.renderer().contextRenderer().addActor(m_actor),m_this.buildTime().modified()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&m_this._build(),m_actor.setVisible(m_this.visible()),m_actor.material().setBinNumber(m_this.bin()),m_this.updateTime().modified()},this._exit=function(){m_this.renderer().contextRenderer().removeActor(m_actor),s_exit()},this._init(arg),this},inherit(geo.gl.contourFeature,geo.contourFeature),geo.registerFeature("vgl","contour",geo.gl.contourFeature),geo.gl.vglRenderer=function(arg){"use strict";if(!(this instanceof geo.gl.vglRenderer))return new geo.gl.vglRenderer(arg);arg=arg||{},geo.renderer.call(this,arg);var m_this=this,m_contextRenderer=null,m_viewer=null,m_width=0,m_height=0,m_renderAnimFrameRef=null,s_init=this._init;return this.width=function(){return m_width},this.height=function(){return m_height},this.contextRenderer=function(){return m_contextRenderer},this.api=function(){return"vgl"},this._init=function(){if(m_this.initialized())return m_this;s_init.call(m_this);var canvas=$(document.createElement("canvas"));canvas.attr("class","webgl-canvas"),$(m_this.layer().node().get(0)).append(canvas),m_viewer=vgl.viewer(canvas.get(0),arg.options),m_viewer.init(),m_contextRenderer=m_viewer.renderWindow().activeRenderer(),m_contextRenderer.setResetScene(!1),m_viewer.renderWindow().renderers().length>0&&m_contextRenderer.setLayer(m_viewer.renderWindow().renderers().length),m_this.canvas(canvas);var map=m_this.layer().map(),mapSize=map.size();return m_this._resize(0,0,mapSize.width,mapSize.height),m_this},this._resize=function(x,y,w,h){var renderWindow=m_viewer.renderWindow();return m_width=w,m_height=h,m_this.canvas().attr("width",w),m_this.canvas().attr("height",h),renderWindow.positionAndResize(x,y,w,h),m_this._updateRendererCamera(),m_this._render(),m_this},this._render=function(){return null===m_renderAnimFrameRef&&(m_renderAnimFrameRef=window.requestAnimationFrame(function(){m_viewer.render(),m_renderAnimFrameRef=null})),m_this},this._updateRendererCamera=function(){var renderWindow=m_viewer.renderWindow(),map=m_this.layer().map(),camera=map.camera(),view=camera.view,proj=camera.projectionMatrix;proj[15]&&(proj=mat4.scale(mat4.create(),proj,[1,1,-1])),proj=mat4.translate(mat4.create(),proj,[0,0,camera.constructor.bounds.far]),renderWindow.renderers().forEach(function(renderer){var cam=renderer.camera();cam.setViewMatrix(view),cam.setProjectionMatrix(proj),proj[1]||proj[2]||proj[3]||proj[4]||proj[6]||proj[7]||proj[8]||proj[9]||proj[11]||1!==proj[15]||parseFloat(map.zoom().toFixed(6))!==parseFloat(map.zoom().toFixed(0))?cam.viewAlignment=function(){return null}:cam.viewAlignment=function(){var align={roundx:2/camera.viewport.width,roundy:2/camera.viewport.height};return align.dx=camera.viewport.width%2?.5*align.roundx:0,align.dy=camera.viewport.height%2?.5*align.roundy:0,align}})},m_this.layer().geoOn(geo.event.pan,function(evt){m_this._updateRendererCamera()}),m_this.layer().geoOn(geo.event.zoom,function(evt){m_this._updateRendererCamera()}),m_this.layer().geoOn(geo.event.parallelprojection,function(evt){var camera,vglRenderer=m_this.contextRenderer(),layer=m_this.layer();evt.geo&&evt.geo._triggeredBy!==layer&&(vglRenderer&&vglRenderer.camera()||console.log("Parallel projection event triggered on unconnected VGL renderer."),camera=vglRenderer.camera(),camera.setEnableParallelProjection(evt.parallelProjection),m_this._updateRendererCamera())}),this},inherit(geo.gl.vglRenderer,geo.renderer),geo.registerRenderer("vgl",geo.gl.vglRenderer),geo.gl.tileLayer=function(){"use strict";var m_this=this;this._drawTile=function(tile){var bounds=this._tileBounds(tile),level=tile.index.level||0,to=this._options.tileOffset(level),ul=this.fromLocal(this.fromLevel({x:bounds.left-to.x,y:bounds.top-to.y},level),0),lr=this.fromLocal(this.fromLevel({x:bounds.right-to.x,y:bounds.bottom-to.y},level),0);tile.feature=m_this.createFeature("plane",{drawOnAsyncResourceLoad:!0}).origin([ul.x,lr.y,1e-7*level]).upperLeft([ul.x,ul.y,1e-7*level]).lowerRight([lr.x,lr.y,1e-7*level]).style({image:tile._image}),tile.feature._update(),m_this.draw()},this._remove=function(tile){tile.feature&&(m_this.deleteFeature(tile.feature),tile.feature=null,m_this.draw())},this._getSubLayer=function(){},this._updateSubLayer=function(){}},geo.registerLayerAdjustment("vgl","tile",geo.gl.tileLayer),geo.gl.choroplethFeature=function(arg){"use strict";function createGLChoropleth(){return m_this.createChoropleth()}if(!(this instanceof geo.gl.choroplethFeature))return new geo.gl.choroplethFeature(arg);arg=arg||{},geo.choroplethFeature.call(this,arg);var m_this=this,m_gl_polygons=null,s_exit=this._exit,s_init=this._init,s_update=this._update;
+return this._init=function(arg){s_init.call(m_this,arg)},this._build=function(){return m_this.buildTime().modified(),m_gl_polygons=createGLChoropleth()},this._update=function(){s_update.call(m_this),(m_this.dataTime().getMTime()>=m_this.buildTime().getMTime()||m_this.updateTime().getMTime()<=m_this.getMTime())&&(m_this._wipePolygons(),m_this._build()),m_this.updateTime().modified()},this._wipePolygons=function(){m_gl_polygons&&m_gl_polygons.map(function(polygon){return polygon._exit()}),m_gl_polygons=null},this._exit=function(){m_this._wipePolygons(),s_exit()},this._init(arg),this},inherit(geo.gl.choroplethFeature,geo.choroplethFeature),geo.registerFeature("vgl","choropleth",geo.gl.choroplethFeature),geo.d3={},function(){"use strict";var chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",strLength=8;geo.d3.uniqueID=function(){var i,strArray=[];for(strArray.length=strLength,i=0;strLength>i;i+=1)strArray[i]=chars.charAt(Math.floor(Math.random()*chars.length));return strArray.join("")},geo.event.d3Rescale="geo_d3_rescale"}(),geo.d3.object=function(arg){"use strict";if(!(this instanceof geo.object))return new geo.d3.object(arg);geo.sceneObject.call(this);var m_id="d3-"+geo.d3.uniqueID(),s_exit=this._exit,m_this=this,s_draw=this.draw;return this._d3id=function(){return m_id},this.select=function(){return m_this.renderer().select(m_this._d3id())},this.draw=function(){return m_this._update(),s_draw(),m_this},this._exit=function(){m_this.renderer()._removeFeature(m_this._d3id()),s_exit()},this},inherit(geo.d3.object,geo.sceneObject),geo.d3.d3Renderer=function(arg){"use strict";function setAttrs(select,attrs){var key;for(key in attrs)attrs.hasOwnProperty(key)&&select.attr(key,attrs[key])}function setStyles(select,styles){function fillFunc(){return styles.fill.apply(m_this,arguments)?null:"none"}function strokeFunc(){return styles.stroke.apply(m_this,arguments)?null:"none"}var key,k,f;for(key in styles)styles.hasOwnProperty(key)&&(f=null,k=null,"strokeColor"===key?(k="stroke",f=m_this._convertColor(styles[key],styles.stroke)):"stroke"===key&&styles[key]?(k="stroke",f=strokeFunc):"strokeWidth"===key?(k="stroke-width",f=m_this._convertScale(styles[key])):"strokeOpacity"===key?(k="stroke-opacity",f=styles[key]):"fillColor"===key?(k="fill",f=m_this._convertColor(styles[key],styles.fill)):"fill"!==key||styles.hasOwnProperty("fillColor")?"fillOpacity"===key&&(k="fill-opacity",f=styles[key]):(k="fill",f=fillFunc),k&&select.style(k,f))}function getGroup(parentId){return parentId?m_svg.select(".group-"+parentId):m_svg.select(".group-"+m_this._d3id())}function initCorners(){var layer=m_this.layer(),map=layer.map(),width=m_this.layer().map().size().width,height=m_this.layer().map().size().height;if(m_width=width,m_height=height,!m_width||!m_height)throw"Map layer has size 0";m_corners={upperLeft:map.displayToGcs({x:0,y:0},null),lowerRight:map.displayToGcs({x:width,y:height},null)}}if(!(this instanceof geo.d3.d3Renderer))return new geo.d3.d3Renderer(arg);geo.renderer.call(this,arg);var s_exit=this._exit;geo.d3.object.call(this,arg),arg=arg||{};var m_this=this,m_sticky=null,m_features={},m_corners=null,m_width=null,m_height=null,m_scale=1,m_dx=0,m_dy=0,m_svg=null,m_defs=null;return this._convertColor=function(f,g){return f=geo.util.ensureFunction(f),g=g||function(){return!0},function(){var c="none";return g.apply(m_this,arguments)&&(c=f.apply(m_this,arguments),c.hasOwnProperty("r")&&c.hasOwnProperty("g")&&c.hasOwnProperty("b")&&(c=d3.rgb(255*c.r,255*c.g,255*c.b))),c}},this._convertPosition=function(f){return f=geo.util.ensureFunction(f),function(){return m_this.layer().map().worldToDisplay(f.apply(m_this,arguments))}},this._convertScale=function(f){return f=geo.util.ensureFunction(f),function(){return f.apply(m_this,arguments)/m_scale}},this._setTransform=function(){if(m_corners||initCorners(),m_sticky){var dx,dy,scale,layer=m_this.layer(),map=layer.map(),upperLeft=map.gcsToDisplay(m_corners.upperLeft,null),lowerRight=map.gcsToDisplay(m_corners.lowerRight,null),group=getGroup(),canvas=m_this.canvas();null!==canvas.attr("scale")?(scale=canvas.attr("scale")||1,dx=(canvas.attr("dx")||0)*scale,dy=(canvas.attr("dy")||0)*scale,dx+=map.size().width/2,dy+=map.size().height/2):(dx=upperLeft.x,dy=upperLeft.y,scale=(lowerRight.y-upperLeft.y)/m_height),group.attr("transform","matrix("+[scale,0,0,scale,dx,dy].join()+")"),m_scale=scale,m_dx=dx,m_dy=dy}},this.baseToLocal=function(pt){return{x:(pt.x-m_dx)/m_scale,y:(pt.y-m_dy)/m_scale}},this.localToBase=function(pt){return{x:pt.x*m_scale+m_dx,y:pt.y*m_scale+m_dy}},this._init=function(arg){if(!m_this.canvas()){var canvas;arg.widget=arg.widget||!1,m_svg="d3Parent"in arg?d3.select(arg.d3Parent).append("svg"):d3.select(m_this.layer().node().get(0)).append("svg"),m_defs=m_svg.append("defs");var shadow=m_defs.append("filter").attr("id","geo-highlight").attr("x","-100%").attr("y","-100%").attr("width","300%").attr("height","300%");shadow.append("feMorphology").attr("operator","dilate").attr("radius",2).attr("in","SourceAlpha").attr("result","dilateOut"),shadow.append("feGaussianBlur").attr("stdDeviation",5).attr("in","dilateOut").attr("result","blurOut"),shadow.append("feColorMatrix").attr("type","matrix").attr("values","-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0").attr("in","blurOut").attr("result","invertOut"),shadow.append("feBlend").attr("in","SourceGraphic").attr("in2","invertOut").attr("mode","normal"),arg.widget||(canvas=m_svg.append("g")),shadow=m_defs.append("filter").attr("id","geo-blur").attr("x","-100%").attr("y","-100%").attr("width","300%").attr("height","300%"),shadow.append("feGaussianBlur").attr("stdDeviation",20).attr("in","SourceGraphic"),m_sticky=m_this.layer().sticky(),m_svg.attr("class",m_this._d3id()),m_svg.attr("width",m_this.layer().node().width()),m_svg.attr("height",m_this.layer().node().height()),arg.widget?m_this.canvas(m_svg):(canvas.attr("class","group-"+m_this._d3id()),m_this.canvas(canvas))}m_this._setTransform()},this.api=function(){return"d3"},this.scaleFactor=function(){return m_scale},this._resize=function(x,y,w,h){m_corners||initCorners(),m_svg.attr("width",w),m_svg.attr("height",h),m_this._setTransform(),m_this.layer().geoTrigger(geo.event.d3Rescale,{scale:m_scale},!0)},this._update=function(){},this._exit=function(){m_features={},m_this.canvas().remove(),s_exit()},this._definitions=function(){return m_defs},this._drawFeatures=function(arg){return m_features[arg.id]={data:arg.data,index:arg.dataIndex,style:arg.style,attributes:arg.attributes,classes:arg.classes,append:arg.append,parentId:arg.parentId},m_this.__render(arg.id,arg.parentId)},this.__render=function(id,parentId){var key;if(void 0===id){for(key in m_features)m_features.hasOwnProperty(key)&&m_this.__render(key);return m_this}var data=m_features[id].data,index=m_features[id].index,style=m_features[id].style,attributes=m_features[id].attributes,classes=m_features[id].classes,append=m_features[id].append,selection=m_this.select(id,parentId).data(data,index);return selection.enter().append(append),selection.exit().remove(),setAttrs(selection,attributes),selection.attr("class",classes.concat([id]).join(" ")),setStyles(selection,style),m_this},this.select=function(id,parentId){return getGroup(parentId).selectAll("."+id)},this._removeFeature=function(id){return m_this.select(id).remove(),delete m_features[id],m_this},this.draw=function(){},this.layer().geoOn(geo.event.pan,m_this._setTransform),this.layer().geoOn(geo.event.zoom,function(){m_this._setTransform(),m_this.__render(),m_this.layer().geoTrigger(geo.event.d3Rescale,{scale:m_scale},!0)}),this.layer().geoOn(geo.event.resize,function(event){m_this._resize(event.x,event.y,event.width,event.height)}),this._init(arg),this},inherit(geo.d3.d3Renderer,geo.renderer),geo.registerRenderer("d3",geo.d3.d3Renderer),geo.d3.tileLayer=function(){"use strict";var m_this=this,s_update=this._update,s_init=this._init;this._drawTile=function(tile){var bounds=m_this._tileBounds(tile),parentNode=m_this._getSubLayer(tile.index.level);tile.feature=m_this.createFeature("plane",{drawOnAsyncResourceLoad:!0}).origin([bounds.left,bounds.top]).upperLeft([bounds.left,bounds.top]).lowerRight([bounds.right,bounds.bottom]).style({image:tile._url,opacity:1,reference:tile.toString(),parentId:parentNode.attr("data-tile-layer-id")}),tile.feature._update(),m_this.draw()},this._getSubLayer=function(level){var node=m_this.canvas().select('g[data-tile-layer="'+level.toFixed()+'"]');if(node.empty()){node=m_this.canvas().append("g");var id=geo.d3.uniqueID();node.classed("group-"+id,!0),node.classed("geo-tile-layer",!0),node.attr("data-tile-layer",level.toFixed()),node.attr("data-tile-layer-id",id)}return node},this._updateSubLayers=function(level){$.each(m_this.canvas().selectAll(".geo-tile-layer")[0],function(idx,el){var layer=parseInt($(el).attr("data-tile-layer"));el=m_this._getSubLayer(layer);var scale=Math.pow(2,level-layer);el.attr("transform","matrix("+[scale,0,0,scale,0,0].join()+")")})},this._init=function(){var sublayer;for(s_init.apply(m_this,arguments),sublayer=0;sublayer<=m_this._options.maxLevel;sublayer+=1)m_this._getSubLayer(sublayer)},this._update=function(){s_update.apply(m_this,arguments),m_this.renderer()._setTransform()},this._remove=function(tile){tile.feature&&(m_this.deleteFeature(tile.feature),tile.feature=null),tile.image&&$(tile.image).remove()}},geo.registerLayerAdjustment("d3","tile",geo.d3.tileLayer),geo.d3.pointFeature=function(arg){"use strict";if(!(this instanceof geo.d3.pointFeature))return new geo.d3.pointFeature(arg);arg=arg||{},geo.pointFeature.call(this,arg),geo.d3.object.call(this);var m_sticky,m_this=this,s_init=this._init,s_update=this._update,m_buildTime=geo.timestamp(),m_style={};return this._init=function(arg){return s_init.call(m_this,arg),m_sticky=m_this.layer().sticky(),m_this},this._build=function(){var data=m_this.data(),s_style=m_this.style.get(),m_renderer=m_this.renderer(),pos_func=m_this.position();return s_update.call(m_this),data||(data=[]),m_style.id=m_this._d3id(),m_style.data=data,m_style.append="circle",m_style.attributes={r:m_renderer._convertScale(s_style.radius),cx:function(d){return m_this.featureGcsToDisplay(pos_func(d)).x},cy:function(d){return m_this.featureGcsToDisplay(pos_func(d)).y}},m_style.style=s_style,m_style.classes=["d3PointFeature"],m_this.renderer()._drawFeatures(m_style),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.pointFeature,geo.pointFeature),geo.registerFeature("d3","point",geo.d3.pointFeature),geo.d3.lineFeature=function(arg){"use strict";if(!(this instanceof geo.d3.lineFeature))return new geo.d3.lineFeature(arg);arg=arg||{},geo.lineFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,s_init=this._init,m_buildTime=geo.timestamp(),s_update=this._update;return this._init=function(arg){return s_init.call(m_this,arg),m_this},this._build=function(){var data=m_this.data()||[],s_style=m_this.style(),m_renderer=m_this.renderer(),pos_func=m_this.position(),line=d3.svg.line().x(function(d){return m_this.featureGcsToDisplay(d).x}).y(function(d){return m_this.featureGcsToDisplay(d).y});return s_update.call(m_this),s_style.fill=function(){return!1},data.forEach(function(item,idx){function wrapStyle(func){return geo.util.isFunction(func)?function(){return func(ln[0],0,item,idx)}:func}var m_style,key,ln=m_this.line()(item,idx),style={};for(key in s_style)s_style.hasOwnProperty(key)&&(style[key]=wrapStyle(s_style[key]));m_style={data:[ln.map(function(d,i){return pos_func(d,i,item,idx)})],append:"path",attributes:{d:line},id:m_this._d3id()+idx,classes:["d3LineFeature","d3SubLine-"+idx],style:style},m_renderer._drawFeatures(m_style)}),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.lineFeature,geo.lineFeature),geo.registerFeature("d3","line",geo.d3.lineFeature),geo.d3.pathFeature=function(arg){"use strict";if(!(this instanceof geo.d3.pathFeature))return new geo.d3.pathFeature(arg);arg=arg||{},geo.pathFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,s_init=this._init,m_buildTime=geo.timestamp(),s_update=this._update,m_style={};return m_style.style={},this._init=function(arg){return s_init.call(m_this,arg),m_this},this._build=function(){var tmp,diag,data=m_this.data()||[],s_style=m_this.style();return s_update.call(m_this),diag=function(d){var p={source:d.source,target:d.target};return d3.svg.diagonal()(p)},tmp=[],data.forEach(function(d,i){var src,trg;i=m_buildTime.getMTime()&&m_this._build(),m_this},this._init(arg),this},inherit(geo.d3.pathFeature,geo.pathFeature),geo.registerFeature("d3","path",geo.d3.pathFeature),geo.d3.graphFeature=function(arg){"use strict";var m_this=this;return this instanceof geo.d3.graphFeature?(geo.graphFeature.call(this,arg),this.select=function(){var renderer=m_this.renderer(),selection={},node=m_this.nodeFeature(),links=m_this.linkFeatures();return selection.nodes=renderer.select(node._d3id()),selection.links=links.map(function(link){return renderer.select(link._d3id())}),selection},this):new geo.d3.graphFeature(arg)},inherit(geo.d3.graphFeature,geo.graphFeature),geo.registerFeature("d3","graph",geo.d3.graphFeature),geo.d3.planeFeature=function(arg){"use strict";function normalize(pt){return Array.isArray(pt)?{x:pt[0],y:pt[1]}:pt}if(!(this instanceof geo.d3.planeFeature))return new geo.d3.planeFeature(arg);geo.planeFeature.call(this,arg),geo.d3.object.call(this);var m_this=this,m_style={},s_update=this._update,s_init=this._init,m_buildTime=geo.timestamp();return this._build=function(){var ul=normalize(m_this.upperLeft()),lr=normalize(m_this.lowerRight()),renderer=m_this.renderer(),s=m_this.style();return delete s.fill_color,delete s.color,delete s.opacity,m_style.id=m_this._d3id(),m_style.style=s,m_style.attributes={x:ul.x,y:ul.y,width:Math.abs(lr.x-ul.x),height:Math.abs(lr.y-ul.y),reference:s.reference},s.image?(m_style.append="image",m_style.attributes["xlink:href"]=s.image):m_style.append="rect",m_style.data=[0],m_style.classes=["d3PlaneFeature"],s.parentId&&(m_style.parentId=s.parentId),renderer._drawFeatures(m_style),m_buildTime.modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.dataTime().getMTime()>=m_buildTime.getMTime()&&m_this._build(),m_this},this._init=function(arg){return s_init.call(m_this,arg||{}),m_this.style({stroke:function(){return!1},fill:function(){return!0},fillColor:function(){return{r:.3,g:.3,b:.3}},fillOpacity:function(){return.5}}),m_this},this._init(),this},inherit(geo.d3.planeFeature,geo.planeFeature),geo.registerFeature("d3","plane",geo.d3.planeFeature),geo.d3.vectorFeature=function(arg){"use strict";function markerID(d,i){return m_this._d3id()+"_marker_"+i}function updateMarkers(data,stroke,opacity){var renderer=m_this.renderer(),sel=m_this.renderer()._definitions().selectAll("marker.geo-vector").data(data);sel.enter().append("marker").attr("id",markerID).attr("class","geo-vector").attr("viewBox","0 0 10 10").attr("refX","1").attr("refY","5").attr("markerWidth","5").attr("markerHeight","5").attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z"),sel.exit().remove(),sel.style("stroke",renderer._convertColor(stroke)).style("fill",renderer._convertColor(stroke)).style("opacity",opacity)}if(!(this instanceof geo.d3.vectorFeature))return new geo.d3.vectorFeature(arg);arg=arg||{},geo.vectorFeature.call(this,arg),geo.d3.object.call(this);var m_sticky,m_this=this,s_init=this._init,s_exit=this._exit,s_update=this._update,m_buildTime=geo.timestamp(),m_style={};return this._init=function(arg){return s_init.call(m_this,arg),m_sticky=m_this.layer().sticky(),m_this},this._build=function(){function getScale(){return scale/m_renderer.scaleFactor()}var data=m_this.data(),s_style=m_this.style.get(),m_renderer=m_this.renderer(),orig_func=m_this.origin(),size_func=m_this.delta(),cache=[],scale=m_this.style("scale"),max=Number.NEGATIVE_INFINITY;return s_update.call(m_this),data||(data=[]),cache=data.map(function(d,i){var origin=m_this.featureGcsToDisplay(orig_func(d,i)),delta=size_func(d,i);return max=Math.max(max,delta.x*delta.x+delta.y*delta.y),{x1:origin.x,y1:origin.y,dx:delta.x,dy:-delta.y}}),max=Math.sqrt(max),scale||(scale=75/max),m_style.id=m_this._d3id(),m_style.data=data,m_style.append="line",m_style.attributes={x1:function(d,i){return cache[i].x1},y1:function(d,i){return cache[i].y1},x2:function(d,i){return cache[i].x1+getScale()*cache[i].dx},y2:function(d,i){return cache[i].y1+getScale()*cache[i].dy},"marker-end":function(d,i){return"url(#"+markerID(d,i)+")"}},m_style.style={stroke:function(){return!0},strokeColor:s_style.strokeColor,strokeWidth:s_style.strokeWidth,strokeOpacity:s_style.strokeOpacity},m_style.classes=["d3VectorFeature"],updateMarkers(data,s_style.strokeColor,s_style.strokeOpacity),m_this.renderer()._drawFeatures(m_style),m_buildTime.modified(),m_this.updateTime().modified(),m_this},this._update=function(){return s_update.call(m_this),m_this.getMTime()>=m_buildTime.getMTime()?m_this._build():updateMarkers(m_style.data,m_style.style.strokeColor,m_style.style.strokeOpacity),m_this},this._exit=function(){s_exit.call(m_this),m_style={},updateMarkers([],null,null)},this._init(arg),this},inherit(geo.d3.vectorFeature,geo.vectorFeature),geo.registerFeature("d3","vector",geo.d3.vectorFeature),geo.gui={},geo.gui.uiLayer=function(arg){"use strict";if(arg.renderer="dom",arg.sticky=!1,!(this instanceof geo.gui.uiLayer))return new geo.gui.uiLayer(arg);geo.layer.call(this,arg);var m_this=this,s_exit=this._exit;this.createWidget=function(widgetName,arg){var newWidget=geo.createWidget(widgetName,m_this,arg);return arg&&"parent"in arg||m_this.addChild(newWidget),newWidget._init(arg),m_this.modified(),newWidget},this.deleteWidget=function(widget){return widget._exit(),m_this.removeChild(widget),m_this.modified(),m_this},this._exit=function(){m_this.children().forEach(function(child){m_this.deleteWidget(child)}),s_exit()}},inherit(geo.gui.uiLayer,geo.layer),geo.registerLayer("ui",geo.gui.uiLayer),geo.gui.widget=function(arg){"use strict";if(!(this instanceof geo.gui.widget))return new geo.gui.widget(arg);geo.sceneObject.call(this,arg);var m_this=this,s_exit=this._exit,m_layer=arg.layer,m_canvas=null;if(arg.position=void 0===arg.position?{left:0,top:0}:arg.position,void 0!==arg.parent&&!(arg.parent instanceof geo.gui.widget))throw"Parent must be of type geo.gui.widget";this._init=function(){m_this.modified()},this._exit=function(){m_this.children().forEach(function(child){m_this._deleteFeature(child)}),m_this.layer().geoOff(geo.event.pan,m_this.repositionEvent),m_this.parentCanvas().removeChild(m_this.canvas()),s_exit()},this._createFeature=function(featureName,arg){var newFeature=geo.createFeature(featureName,m_this,m_this.renderer(),arg);return m_this.addChild(newFeature),m_this.modified(),newFeature},this._deleteFeature=function(feature){return m_this.removeChild(feature),feature._exit(),m_this},this.layer=function(){return m_layer},this._createCanvas=function(){throw"Must be defined in derived classes"},this.canvas=function(val){return void 0===val?m_canvas:void(m_canvas=val)},this._appendChild=function(){m_this.parentCanvas().appendChild(m_this.canvas())},this.parentCanvas=function(){return void 0===m_this.parent?m_this.layer().canvas():m_this.parent().canvas()},this.position=function(){var position;return arg&&arg.hasOwnProperty("position")&&arg.position.hasOwnProperty("x")&&arg.position.hasOwnProperty("y")?(position=m_this.layer().map().gcsToDisplay(arg.position),{left:position.x,top:position.y}):arg.position},this.reposition=function(position){position=position||m_this.position(),m_this.canvas().style.position="absolute";for(var cssAttr in position)position.hasOwnProperty(cssAttr)&&(m_this.canvas().style[cssAttr]=position[cssAttr]+"px")},this.repositionEvent=function(){return m_this.reposition()},this.isInViewport=function(){var position=m_this.position(),layer=m_this.layer();return position.left>=0&&position.top>=0&&position.left<=layer.width()&&position.top<=layer.height()},arg&&arg.hasOwnProperty("position")&&arg.position.hasOwnProperty("x")&&arg.position.hasOwnProperty("y")&&this.layer().geoOn(geo.event.pan,m_this.repositionEvent)},inherit(geo.gui.widget,geo.sceneObject),geo.gui.domWidget=function(arg){"use strict";if(!(this instanceof geo.gui.domWidget))return new geo.gui.domWidget(arg);geo.gui.widget.call(this,arg);var m_this=this,m_default_canvas="div";return this._init=function(){arg.hasOwnProperty("parent")&&arg.parent.addChild(m_this),m_this._createCanvas(),m_this._appendChild(),m_this.canvas().addEventListener("mousedown",function(e){e.stopPropagation()}),m_this.reposition()},this._createCanvas=function(){m_this.canvas(document.createElement(arg.el||m_default_canvas))},this},inherit(geo.gui.domWidget,geo.gui.widget),geo.registerWidget("dom","dom",geo.gui.domWidget),geo.gui.svgWidget=function(arg){"use strict";if(!(this instanceof geo.gui.svgWidget))return new geo.gui.svgWidget(arg);geo.gui.domWidget.call(this,arg);var m_this=this,m_renderer=null;return this._init=function(arg){var d3Parent;arg.hasOwnProperty("parent")&&(arg.parent.addChild(m_this),d3Parent=arg.parent.canvas()),m_this._createCanvas(d3Parent),m_this.canvas().addEventListener("mousedown",function(e){e.stopPropagation()}),m_this.reposition()},this._createCanvas=function(d3Parent){var rendererOpts={layer:m_this.layer(),widget:!0};d3Parent&&(rendererOpts.d3Parent=d3Parent),m_renderer=geo.d3.d3Renderer(rendererOpts),m_this.canvas(m_renderer.canvas()[0][0])},this},inherit(geo.gui.svgWidget,geo.gui.domWidget),geo.registerWidget("dom","svg",geo.gui.svgWidget),geo.gui.sliderWidget=function(arg){"use strict";function put_icon(icon,base,cx,cy,size){var g=base.append("g"),s=size/1024;return g.append("g").append("g").attr("transform","translate("+cx+","+cy+") scale("+s+") translate(-512,-512)").append("path").attr("d",icon).attr("class","geo-glyphicon"),g}if(!(this instanceof geo.gui.sliderWidget))return new geo.gui.sliderWidget(arg);geo.gui.svgWidget.call(this,arg);var m_xscale,m_yscale,m_plus,m_minus,m_track,m_nub,m_plusIcon,m_minusIcon,m_group,m_lowContrast,m_this=this,s_exit=this._exit,s_createCanvas=this._createCanvas,s_appendChild=this._appendChild,m_width=20,m_height=100,m_nubSize=10,m_highlightDur=100;m_plusIcon="M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM564.326 564.326v206.182h-104.653v-206.182h-206.234v-104.653h206.182v-206.234h104.704v206.182h206.182v104.704h-206.182z",m_minusIcon="M512 81.92c-237.568 0-430.080 192.614-430.080 430.080 0 237.568 192.563 430.080 430.080 430.080s430.080-192.563 430.080-430.080c0-237.517-192.563-430.080-430.080-430.080zM770.56 459.674v104.704h-517.12v-104.704h517.12z",m_lowContrast={white:"#f4f4f4",black:"#505050"},this._init=function(){function respond(evt,trans){var z=m_yscale.invert(d3.mouse(m_this.layer().node()[0])[1]),zrange=map.zoomRange();z=(1-z)*(zrange.max-zrange.min)+zrange.min,trans?map.transition({zoom:z,ease:d3.ease("cubic-in-out"),duration:500,done:m_this._update()}):(map.zoom(z),m_this._update()),evt.stopPropagation()}s_createCanvas(),s_appendChild(),m_this.reposition();var svg=d3.select(m_this.canvas()),x0=40,y0=40+m_width,map=m_this.layer().map();m_xscale=d3.scale.linear().domain([-4,4]).range([x0,x0+m_width]),m_yscale=d3.scale.linear().domain([0,1]).range([y0,y0+m_height]),svg=svg.append("g").classed("geo-ui-slider",!0),m_group=svg,m_plus=svg.append("g"),m_plus.append("circle").datum({fill:"white",stroke:null}).classed("geo-zoom-in",!0).attr("cx",m_xscale(0)).attr("cy",m_yscale(0)-m_width+2).attr("r",m_width/2).style({cursor:"pointer"}).on("click",function(){var z=map.zoom();map.transition({zoom:z+1,ease:d3.ease("cubic-in-out"),duration:500})}).on("mousedown",function(){d3.event.stopPropagation()}),put_icon(m_plusIcon,m_plus,m_xscale(0),m_yscale(0)-m_width+2,m_width+5).style("cursor","pointer").style("pointer-events","none").select("path").datum({fill:"black",stroke:null}),m_minus=svg.append("g"),m_minus.append("circle").datum({fill:"white",stroke:null}).classed("geo-zoom-out",!0).attr("cx",m_xscale(0)).attr("cy",m_yscale(1)+m_width-2).attr("r",m_width/2).style({cursor:"pointer"}).on("click",function(){var z=map.zoom();map.transition({zoom:z-1,ease:d3.ease("cubic-in-out"),duration:500})}).on("mousedown",function(){d3.event.stopPropagation()}),put_icon(m_minusIcon,m_minus,m_xscale(0),m_yscale(1)+m_width-2,m_width+5).style("cursor","pointer").style("pointer-events","none").select("path").datum({fill:"black",stroke:null}),m_track=svg.append("rect").datum({fill:"white",stroke:"black"}).classed("geo-zoom-track",!0).attr("x",m_xscale(0)-m_width/6).attr("y",m_yscale(0)).attr("rx",m_width/10).attr("ry",m_width/10).attr("width",m_width/3).attr("height",m_height).style({cursor:"pointer"}).on("click",function(){respond(d3.event,!0)}),m_nub=svg.append("rect").datum({fill:"black",stroke:null}).classed("geo-zoom-nub",!0).attr("x",m_xscale(-4)).attr("y",m_yscale(.5)-m_nubSize/2).attr("rx",3).attr("ry",3).attr("width",m_width).attr("height",m_nubSize).style({cursor:"pointer"}).on("mousedown",function(){d3.select(document).on("mousemove.geo.slider",function(){respond(d3.event)}),d3.select(document).on("mouseup.geo.slider",function(){respond(d3.event),d3.select(document).on(".geo.slider",null)}),d3.event.stopPropagation()});var mouseOver=function(){d3.select(this).attr("filter","url(#geo-highlight)"),m_group.selectAll("rect,path,circle").transition().duration(m_highlightDur).style("fill",function(d){return d.fill||null}).style("stroke",function(d){return d.stroke||null})},mouseOut=function(){d3.select(this).attr("filter",null),m_group.selectAll("circle,rect,path").transition().duration(m_highlightDur).style("fill",function(d){return m_lowContrast[d.fill]||null}).style("stroke",function(d){return m_lowContrast[d.stroke]||null})};m_group.selectAll("*").on("mouseover",mouseOver).on("mouseout",mouseOut),m_this.layer().geoOn(geo.event.zoom,function(){m_this._update()}),mouseOut(),m_this._update()},this._exit=function(){m_group.remove(),m_this.layer().geoOff(geo.event.zoom),s_exit()},this._update=function(obj){var map=m_this.layer().map(),zoomRange=map.zoomRange(),zoom=map.zoom(),zoomScale=d3.scale.linear();obj=obj||{},zoom=obj.value||zoom,zoomScale.domain([zoomRange.min,zoomRange.max]).range([1,0]).clamp(!0),m_nub.attr("y",m_yscale(zoomScale(zoom))-m_nubSize/2)}},inherit(geo.gui.sliderWidget,geo.gui.svgWidget),geo.registerWidget("dom","slider",geo.gui.sliderWidget),geo.gui.legendWidget=function(arg){"use strict";if(!(this instanceof geo.gui.legendWidget))return new geo.gui.legendWidget(arg);geo.gui.svgWidget.call(this,arg);var m_this=this,m_categories=[],m_top=null,m_group=null,m_border=null,m_spacing=20,m_padding=12,s_createCanvas=this._createCanvas,s_appendChild=this._appendChild;this.categories=function(arg){return void 0===arg?m_categories.slice():(m_categories=arg.slice().map(function(d){return"line"===d.type&&(d.style.fill=!1,d.style.stroke=!0),d}),m_this.draw(),m_this)},this.size=function(){var height,width=1,test=d3.select(m_this.canvas()).append("text").style("opacity",1e-6);return m_categories.forEach(function(d){test.text(d.name),width=Math.max(width,test.node().getBBox().width)}),test.remove(),height=m_spacing*(m_categories.length+1),{width:width+50,height:height}},this.draw=function(){function applyColor(selection){selection.style("fill",function(d){return d.style.fill||void 0===d.style.fill?d.style.fillColor:"none"}).style("fill-opacity",function(d){return void 0===d.style.fillOpacity?1:d.style.fillOpacity}).style("stroke",function(d){return d.style.stroke||void 0===d.style.stroke?d.style.strokeColor:"none"}).style("stroke-opacity",function(d){return void 0===d.style.strokeOpacity?1:d.style.strokeOpacity}).style("stroke-width",function(d){return void 0===d.style.strokeWidth?1.5:d.style.strokeWidth})}m_this._init(),m_border.attr("height",m_this.size().height+2*m_padding).style("display",null);var scale=m_this._scale(),labels=m_group.selectAll("g.geo-label").data(m_categories,function(d){return d.name}),g=labels.enter().append("g").attr("class","geo-label").attr("transform",function(d,i){return"translate(0,"+scale.y(i)+")"});return applyColor(g.filter(function(d){return"point"!==d.type&&"line"!==d.type}).append("rect").attr("x",0).attr("y",-6).attr("rx",5).attr("ry",5).attr("width",40).attr("height",12)),applyColor(g.filter(function(d){return"point"===d.type}).append("circle").attr("cx",20).attr("cy",0).attr("r",6)),applyColor(g.filter(function(d){return"line"===d.type}).append("line").attr("x1",0).attr("y1",0).attr("x2",40).attr("y2",0)),g.append("text").attr("x","50px").attr("y",0).attr("dy","0.3em").text(function(d){return d.name}),m_this.reposition(),m_this},this._scale=function(){return{x:d3.scale.linear().domain([0,1]).range([0,m_this.size().width]),y:d3.scale.linear().domain([0,m_categories.length-1]).range([m_padding/2,m_this.size().height-m_padding/2])}},this._init=function(){m_top||(s_createCanvas(),s_appendChild());var w=m_this.size().width+2*m_padding+4,h=m_this.size().height+2*m_padding+4;m_top&&m_top.remove(),d3.select(m_this.canvas()).attr("width",w).attr("height",h),m_top=d3.select(m_this.canvas()).append("g"),m_group=m_top.append("g").attr("transform","translate("+[m_padding+2,m_padding+2]+")"),m_border=m_group.append("rect").attr("x",-m_padding).attr("y",-m_padding).attr("width",w-4).attr("height",h-4).attr("rx",3).attr("ry",3).style({stroke:"black","stroke-width":"1.5px",fill:"white","fill-opacity":.75,display:"none"}),m_group.on("mousedown",function(){d3.event.stopPropagation()}),m_group.on("mouseover",function(){m_border.transition().duration(250).style("fill-opacity",1)}),m_group.on("mouseout",function(){m_border.transition().duration(250).style("fill-opacity",.75)}),m_this.reposition()},this.geoOn(geo.event.resize,function(){m_this.draw()})},inherit(geo.gui.legendWidget,geo.gui.svgWidget),geo.registerWidget("dom","legend",geo.gui.legendWidget),function($,geo,d3){"use strict";var load=function(){function isColorKey(key){return"color"===key.slice(key.length-5,key.length).toLowerCase()}function makeColorScale(data,acc){function wrap(func){return geo.util.isFunction(func)?function(){return func(acc.apply(this,arguments))}:func(acc)}if(!d3)return console.warn("d3 is unavailable, cannot apply color scales."),acc;var domain,cannotHandle=!1,doNotHandle=!0,categorical=!1,min=Number.POSITIVE_INFINITY,max=Number.NEGATIVE_INFINITY;return domain=geo.util.isFunction(acc)?d3.set(data.map(acc)).values():[acc],domain.forEach(function(v){("string"!=typeof v||"object"!=typeof geo.util.convertColor(v))&&(doNotHandle=!1),"string"==typeof v?categorical=!0:isFinite(v)?+v>max?max=+v:min>+v&&(min=+v):cannotHandle=!0}),cannotHandle?acc:doNotHandle?acc:wrap(categorical?domain.length<=10?d3.scale.category10().domain(domain):domain.length<=20?d3.scale.category20().domain(domain):d3.scale.category20().domain(domain):d3.scale.linear().range(["rgb(252,141,89)","rgb(255,255,191)","rgb(145,191,219)"]).domain([min,(min+max)/2,max]))}return $.widget?void $.widget("geojs.geojsMap",{
+options:{center:{latitude:0,longitude:0},zoom:0,width:null,height:null,layers:[],data:[],url:"http://tile.openstreetmap.org/{z}/{x}/{y}.png",attribution:void 0,baseLayer:"osm",baseRenderer:"vgl"},_create:function(){!this._map&&this.element.length&&(this._map=geo.map({width:this.options.width,height:this.options.height,zoom:this.options.zoom,center:this.options.center,node:this.element.get(0)}),this._baseLayer=this._map.createLayer(this.options.baseLayer,{renderer:this.options.baseRenderer,url:this.options.url,attribution:this.options.attribution}),this._resize({width:800,height:600}),this._layers=[],this.update())},update:function(layers){var m_this=this;return this.options.layers=layers||this.options.layers||[],this._layers.forEach(function(layer){layer.clear(),m_this._map.deleteLayer(layer)}),this._layers=this.options.layers.map(function(layer){return layer.data=layer.data||m_this.options.data,(layer.features||[]).forEach(function(feature){var scl,data=feature.data||layer.data||[];"point"===feature.type&&(feature.size?feature._size=geo.util.ensureFunction(feature.size):null===feature.size&&delete feature._size,data.length&&feature._size&&(scl=d3.scale.linear().domain(d3.extent(data,feature._size)).range([5,20]),feature.radius=function(){return scl(feature._size.apply(this,arguments))}),delete feature.size);var key;for(key in feature)feature.hasOwnProperty(key)&&isColorKey(key)&&(feature[key]=makeColorScale(data,feature[key]))}),geo.layer.create(m_this._map,layer)}),this.redraw(),this},map:function(){return this._map},url:function(url){return this._baseLayer.url(url),this},_resize:function(size){var width=this.options.width,height=this.options.height;size&&(width=size.width,height=size.height),width||(width=this.element.width()),height||(height=this.element.height()),this._map.resize(0,0,width,height)},redraw:function(){return this._resize(),this}}):void($.fn.geojsMap=function(){throw new Error("The geojs jquery plugin requires jquery ui to be available.")})};$(load)}($||window.$,geo||window.geo,d3||window.d3);
\ No newline at end of file
diff --git a/package.json b/package.json
index 0aba10ee66..744721979d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "geojs",
- "version": "0.5.0",
+ "version": "0.6.0-rc.1",
"description": "JavaScript Geo visualization and Analysis Library",
"homepage": "https://github.com/OpenGeoscience/geojs",
"license": "Apache-2.0",