diff options
Diffstat (limited to 'js/dojo/dojox/geo')
34 files changed, 4876 insertions, 0 deletions
diff --git a/js/dojo/dojox/geo/README b/js/dojo/dojox/geo/README new file mode 100644 index 0000000..76b3f9b --- /dev/null +++ b/js/dojo/dojox/geo/README @@ -0,0 +1,43 @@ +------------------------------------------------------------------------------- +dojox.geo +------------------------------------------------------------------------------- +Version 0.100 +Release date: 04/04/2010 +------------------------------------------------------------------------------- +Project state: +alpha +------------------------------------------------------------------------------- +Credits + dojox.geo.charting: + Dean Williams (deanw@ca.ibm.com) + Qi Ruan (ruanqi@cn.ibm.com) + Wei CDL Huang (hwcdl@cn.ibm.com) + Erwan Aullas (eaullas+dojo@gmail.com) + Marc Durocher (durocher.marc@gmail.com) + + dojox.geo.openlayers + Marc Durocher (durocher.marc@gmail.com) + Erwan Aullas (eaullas+dojo@gmail.com) + + Tom Trenka (ttrenka@gmail.com) +------------------------------------------------------------------------------- +Project description + +Project to house any geographical-related code. First project is +dojox.geo.charting, contributed to DTK by IBM (CCLA on file). +------------------------------------------------------------------------------- +Dependencies: + +Dojo Core, dojox.gfx, dojox.lang, Dijit. +------------------------------------------------------------------------------- +Documentation + +Not ready yet. +------------------------------------------------------------------------------- +Installation instructions + +Grab the following from the Dojo SVN Repository: +http://svn.dojotoolkit.org/src/dojox/trunk/geo + +You will need dojox.gfx, dojox.lang, and the Dijit core + Tooltip. +------------------------------------------------------------------------------- diff --git a/js/dojo/dojox/geo/charting/Feature.js b/js/dojo/dojox/geo/charting/Feature.js new file mode 100644 index 0000000..4bfc059 --- /dev/null +++ b/js/dojo/dojox/geo/charting/Feature.js @@ -0,0 +1,199 @@ +//>>built +define("dojox/geo/charting/Feature", ["dojo/_base/lang", "dojo/_base/declare","dojo/_base/array", + "dojo/_base/html","dojo/dom","dojo/_base/event", "dojox/gfx/fx", "dojox/color"], + function(lang, declare,arr, html,dom, event, fx,color) { + +return declare("dojox.geo.charting.Feature", null, { + // summary: + // class to encapsulate a map element. + // tags: + // private + + _isZoomIn: false, + isSelected: false, + markerText:null, + + + constructor: function(parent, name, shapeData){ + // summary: + // constructs a new Feature. + // tags: + // private + this.id = name; + this.shape = parent.mapObj.createGroup(); + this.parent = parent; + this.mapObj = parent.mapObj; + this._bbox = shapeData.bbox; + this._center = shapeData.center; + //TODO: fill color would be defined by charting data and legend +// this._highlightFill = ["#FFCE52", "#CE6342", "#63A584"][Math.floor(Math.random() * 3)]; + this._defaultFill = parent.defaultColor; + this._highlightFill = parent.highlightColor; + this._defaultStroke = { + width: this._normalizeStrokeWeight(.5), + color: "white" + }; + + var shapes = (lang.isArray(shapeData.shape[0])) ? shapeData.shape : [shapeData.shape]; + arr.forEach(shapes, function(points){ + this.shape.createPolyline(points).setStroke(this._defaultStroke); + }, this); + this.unsetValue(); + }, + unsetValue:function(){ + // summary: + // clears the numeric value on this Feature object (removes color). + + this.value = null; + this.unsetColor(); + }, + unsetColor:function(){ + this._defaultFill = this.parent.defaultColor; + var col = new color.Color(this.parent.defaultColor).toHsl(); + col.l = 1.2 * col.l; + this._highlightFill = color.fromHsl(col); + this._setFillWith(this._defaultFill); + }, + setValue:function(value){ + // summary: + // sets a numeric value on this Feature object (used together with series to apply a color). + // value: + // a number + this.value = value; + if (value == null) { + this.unsetValue(); + } else { + if (this.parent.series.length != 0) { + for (var i = 0; i < this.parent.series.length; i++) { + var range = this.parent.series[i]; + if ((value >= range.min) && (value < range.max)) { + this._setFillWith(range.color); + this._defaultFill = range.color; + var col = new color.Color(range.color).toHsv(); + col.v = (col.v + 20); + this._highlightFill = color.fromHsv(col); + return; + } + } + // did not found a range : unset color + this.unsetColor(); + } + } + }, + _setFillWith: function(color){ + var borders = (lang.isArray(this.shape.children)) ? this.shape.children : [this.shape.children]; + arr.forEach(borders, lang.hitch(this,function(item){ + if(this.parent.colorAnimationDuration > 0){ + var anim1 = fx.animateFill({ + shape: item, + color: { + start: item.getFill(), + end: color + }, + duration: this.parent.colorAnimationDuration + }); + anim1.play(); + }else{ + item.setFill(color); + } + })); + }, + _setStrokeWith: function(stroke){ + var borders = (lang.isArray(this.shape.children)) ? this.shape.children : [this.shape.children]; + arr.forEach(borders, function(item){ + item.setStroke({ + color: stroke.color, + width: stroke.width, + join: "round" + }); + }); + }, + _normalizeStrokeWeight: function(weight){ + var matrix = this.shape._getRealMatrix(); + return (dojox.gfx.renderer != "vml")?weight/(this.shape._getRealMatrix()||{xx:1}).xx:weight; + }, + _onmouseoverHandler: function(evt){ + this.parent.onFeatureOver(this); + this._setFillWith(this._highlightFill); + this.mapObj.marker.show(this.id,evt); + }, + _onmouseoutHandler: function(){ + this._setFillWith(this._defaultFill); + this.mapObj.marker.hide(); + html.style("mapZoomCursor", "display", "none"); + }, + _onmousemoveHandler: function(evt){ + if(this.mapObj.marker._needTooltipRefresh){ + this.mapObj.marker.show(this.id,evt); + } + if(this.isSelected){ + if (this.parent.enableFeatureZoom) { + evt = event.fix(evt || window.event); + html.style("mapZoomCursor", "left", evt.pageX + 12 + "px"); + html.style("mapZoomCursor", "top", evt.pageY + "px"); + html.byId("mapZoomCursor").className = this._isZoomIn ? "mapZoomOut":"mapZoomIn"; + html.style("mapZoomCursor", "display", "block"); + }else{ + html.style("mapZoomCursor", "display", "none"); + } + } + }, + _onclickHandler: function(evt){ + this.parent.onFeatureClick(this); + if(!this.isSelected){ + this.parent.deselectAll(); + this.select(true); + this._onmousemoveHandler(evt); + }else if(this.parent.enableFeatureZoom){ + if(this._isZoomIn){ + this._zoomOut(); + }else{ + this._zoomIn(); + } + } + }, + + select: function(selected) { + if(selected){ + this.shape.moveToFront(); + this._setStrokeWith({color:"black",width:this._normalizeStrokeWeight(2)}); + this._setFillWith(this._highlightFill); + this.isSelected = true; + this.parent.selectedFeature = this; + }else{ + this._setStrokeWith(this._defaultStroke); + this._setFillWith(this._defaultFill); + this.isSelected = false; + this._isZoomIn = false; + } + }, + + _zoomIn: function(){ + var marker = this.mapObj.marker; + marker.hide(); + this.parent.fitToMapArea(this._bbox, 15,true,lang.hitch(this,function(){ + this._setStrokeWith({color:"black",width:this._normalizeStrokeWeight(2)}); + marker._needTooltipRefresh = true; + this.parent.onZoomEnd(this); + })); + this._isZoomIn = true; + dom.byId("mapZoomCursor").className = ""; + }, + _zoomOut: function(){ + var marker = this.mapObj.marker; + marker.hide(); + this.parent.fitToMapContents(3,true,lang.hitch(this,function(){ + this._setStrokeWith({color:"black",width:this._normalizeStrokeWeight(2)}); + marker._needTooltipRefresh = true; + this.parent.onZoomEnd(this); + })); + this._isZoomIn = false; + dom.byId("mapZoomCursor").className = ""; + }, + + init: function(){ + this.shape.id = this.id; + this.tooltip = null; + } +}); +});
\ No newline at end of file diff --git a/js/dojo/dojox/geo/charting/KeyboardInteractionSupport.js b/js/dojo/dojox/geo/charting/KeyboardInteractionSupport.js new file mode 100644 index 0000000..ef3e887 --- /dev/null +++ b/js/dojo/dojox/geo/charting/KeyboardInteractionSupport.js @@ -0,0 +1,145 @@ +//>>built +define("dojox/geo/charting/KeyboardInteractionSupport", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/event","dojo/_base/connect", + "dojo/_base/html","dojo/dom","dojox/lang/functional","dojo/keys"], + function(lang, declare, event, connect, html, dom, functional, keys) { + +return declare("dojox.geo.charting.KeyboardInteractionSupport", null, { + // summary: + // class to handle keyboard interactions on a dojox.geo.charting.Map widget + // + // The sections on the leading edge should receive the focus in response to a TAB event. + // Then use cursor keys to the peer sections. The cursor event should go the adjacent section + // in that direction. With the focus, the section zooms in upon SPACE. The map should zoom out + // on ESC. Finally, while it has the focus, the map should lose the focus on TAB. + // tags: + // private + _map: null, + _zoomEnabled: false, + + constructor: function(map, options){ + // summary: + // Constructs a new _KeyboardInteractionSupport instance + // map: dojox.geo.charting.Map + // the Map widget this class provides touch navigation for. + this._map = map; + if(options){ + this._zoomEnabled = options.enableZoom; + } + }, + connect: function(){ + // summary: + // connects this keyboard support class to the Map component + var container = dom.byId(this._map.container); + // tab accessing enable + html.attr(container, { + tabindex: 0, + role: "presentation", + "aria-label": "map" + }); + // install listeners + this._keydownListener = connect.connect(container, "keydown", this, "keydownHandler"); + this._onFocusListener = connect.connect(container, "focus", this, "onFocus"); + this._onBlurListener = connect.connect(container, "blur", this, "onBlur"); + }, + disconnect: function(){ + // summary: + // disconnects any installed listeners + connect.disconnect(this._keydownListener); + this._keydownListener = null; + connect.disconnect(this._onFocusListener); + this._onFocusListener = null; + connect.disconnect(this._onBlurListener); + this._onBlurListener = null + }, + keydownHandler: function(e){ + switch(e.keyCode){ + case keys.LEFT_ARROW: + this._directTo(-1,-1,1,-1); + break; + case keys.RIGHT_ARROW: + this._directTo(-1,-1,-1,1); + break; + case keys.UP_ARROW: + this._directTo(1,-1,-1,-1); + break; + case keys.DOWN_ARROW: + this._directTo(-1,1,-1,-1); + break; + case keys.SPACE: + if(this._map.selectedFeature && !this._map.selectedFeature._isZoomIn && this._zoomEnabled){ + this._map.selectedFeature._zoomIn(); + } + break; + case keys.ESCAPE: + if(this._map.selectedFeature && this._map.selectedFeature._isZoomIn && this._zoomEnabled){ + this._map.selectedFeature._zoomOut(); + } + break; + default: + return; + } + event.stop(e); + }, + onFocus: function(e){ + // select the leading region at the map center + if(this._map.selectedFeature || this._map.focused){return;} + this._map.focused = true; + var leadingRegion, + needClick = false; + if(this._map.lastSelectedFeature){ + leadingRegion = this._map.lastSelectedFeature; + }else{ + var mapCenter = this._map.getMapCenter(), + minDistance = Infinity; + // find the region most closing to the map center + functional.forIn(this._map.mapObj.features, function(feature){ + var distance = Math.sqrt(Math.pow(feature._center[0] - mapCenter.x, 2) + Math.pow(feature._center[1] - mapCenter.y, 2)); + if(distance < minDistance){ + minDistance = distance; + leadingRegion = feature; + } + }); + needClick = true; + } + if(leadingRegion){ + if(needClick) { + leadingRegion._onclickHandler(null); + }else{ + } + this._map.mapObj.marker.show(leadingRegion.id); + } + }, + onBlur: function(){ + this._map.lastSelectedFeature = this._map.selectedFeature; + }, + _directTo: function(up,down,left,right){ + var currentSelected = this._map.selectedFeature, + centerX = currentSelected._center[0], + centerY = currentSelected._center[1], + minMargin = Infinity, + nextSelected = null; + functional.forIn(this._map.mapObj.features, function(feature){ + var paddingX = Math.abs(centerX - feature._center[0]), + paddingY = Math.abs(centerY - feature._center[1]), + paddingSum = paddingX + paddingY; + if((up - down) * (centerY - feature._center[1]) > 0){ + if(paddingX < paddingY && minMargin > paddingSum){ + minMargin = paddingSum; + nextSelected = feature; + } + } + if((left - right) * (centerX - feature._center[0]) > 0){ + if(paddingX > paddingY && minMargin > paddingSum){ + minMargin = paddingSum; + nextSelected = feature; + } + } + }); + if(nextSelected){ + this._map.mapObj.marker.hide(); + nextSelected._onclickHandler(null); + this._map.mapObj.marker.show(nextSelected.id); + } + } +}); +});
\ No newline at end of file diff --git a/js/dojo/dojox/geo/charting/Map.js b/js/dojo/dojox/geo/charting/Map.js new file mode 100644 index 0000000..d0e966c --- /dev/null +++ b/js/dojo/dojox/geo/charting/Map.js @@ -0,0 +1,604 @@ +//>>built +define("dojox/geo/charting/Map", ["dojo/_base/lang","dojo/_base/array","dojo/_base/declare","dojo/_base/html","dojo/dom", + "dojo/dom-geometry","dojo/dom-class", "dojo/_base/xhr","dojo/_base/connect","dojo/_base/window", "dojox/gfx", + "dojox/geo/charting/_base","dojox/geo/charting/Feature","dojox/geo/charting/_Marker","dojo/number","dojo/_base/sniff"], + function(lang, arr, declare, html, dom, domGeom, domClass, xhr, connect, win, gfx, base, + Feature, Marker, number, has) { + + return declare("dojox.geo.charting.Map", null, { + // summary: + // Map widget interacted with charting. + // description: + // Support rendering Americas, AsiaPacific, ContinentalEurope, EuropeMiddleEastAfrica, + // USStates, WorldCountries, and WorldCountriesMercator by default. + // example: + // | var usaMap = new dojox.geo.charting.Map(srcNode, "dojotoolkit/dojox/geo/charting/resources/data/USStates.json"); + // | <div id="map" style="width:600px;height:400px;"></div> + + // defaultColor: String + // Default map feature color, e.g: "#B7B7B7" + defaultColor:"#B7B7B7", + // highlightColor: String + // Map feature color when mouse over it, e.g: "#" + highlightColor:"#D5D5D5", + // series: Array + // stack to data range, e.g: [{name:'label 1', min:20, max:70, color:'#DDDDDD'},{...},...] + series:[], + dataBindingAttribute:null, + dataBindingValueFunction:null, + dataStore:null, + showTooltips: true, + enableFeatureZoom: true, + colorAnimationDuration:0, + _idAttributes:null, + _onSetListener:null, + _onNewListener:null, + _onDeleteListener:null, + constructor: function(/*HTML Node*/container, /*String or Json object*/shapeData){ + // container: + // map container html node/id + // shapeData: + // map shape data json object, or url to json file + + html.style(container, "display", "block"); + + this.container = container; + var containerBounds = this._getContainerBounds(); + // get map container coords + this.surface = gfx.createSurface(container, containerBounds.w, containerBounds.h); + + this._createZoomingCursor(); + + // add transparent background for event capture + this.mapBackground = this.surface.createRect({x: 0, y: 0, width: containerBounds.w, height: containerBounds.w}).setFill("rgba(0,0,0,0)"); + + this.mapObj = this.surface.createGroup(); + this.mapObj.features = {}; + + if (typeof shapeData == "object") { + this._init(shapeData); + } else { + // load map shape file + if (typeof shapeData == "string" && shapeData.length > 0) { + xhr.get({ + url: shapeData, + handleAs: "json", + sync: true, + load: lang.hitch(this, "_init") + }); + } + } + }, + + _getContainerBounds: function() { + // summary: + // returns the bounds {x:, y:, w: ,h:} of the DOM node container in absolute coordinates + // tags: + // private + + var position = domGeom.position(this.container,true); + var marginBox = domGeom.getMarginBox(this.container); + // use contentBox for correct width and height - surface spans outside border otherwise + var contentBox = domGeom.getContentBox(this.container); + this._storedContainerBounds = { + x: position.x, + y: position.y, + w: contentBox.w || 100, + h: contentBox.h || 100 + }; + return this._storedContainerBounds; + }, + + resize: function(/**boolean**/ adjustMapCenter/**boolean**/,adjustMapScale,/**boolean**/ animate) { + // summary: + // resize the underlying GFX surface to accommodate to parent DOM Node size change + // adjustMapCenter: boolean + // keeps the center of the map when resizing the surface + // adjustMapScale: boolean + // adjusts the map scale to keep the visible portion of the map as much as possible + + var oldBounds = this._storedContainerBounds; + var newBounds = this._getContainerBounds(); + + if ((oldBounds.w == newBounds.w) && (oldBounds.h == newBounds.h)) { + return; + } + + // set surface dimensions, and background + this.mapBackground.setShape({width:newBounds.w, height:newBounds.h}); + this.surface.setDimensions(newBounds.w,newBounds.h); + + this.mapObj.marker.hide(); + this.mapObj.marker._needTooltipRefresh = true; + + if (adjustMapCenter) { + + var mapScale = this.getMapScale(); + var newScale = mapScale; + + if (adjustMapScale) { + var bbox = this.mapObj.boundBox; + var widthFactor = newBounds.w / oldBounds.w; + var heightFactor = newBounds.h / oldBounds.h; + newScale = mapScale * Math.sqrt(widthFactor * heightFactor); + } + + // current map center + var invariantMapPoint = this.screenCoordsToMapCoords(oldBounds.w/2,oldBounds.h/2); + + // apply new parameters + this.setMapCenterAndScale(invariantMapPoint.x,invariantMapPoint.y,newScale,animate); + } + }, + + _isMobileDevice: function() { + // summary: + // tests whether the application is running on a mobile device (android or iOS) + // tags: + // private + return (has("safari") + && (navigator.userAgent.indexOf("iPhone") > -1 || + navigator.userAgent.indexOf("iPod") > -1 || + navigator.userAgent.indexOf("iPad") > -1 + )) || (navigator.userAgent.toLowerCase().indexOf("android") > -1); + }, + + + setMarkerData: function(/*String*/ markerFile){ + // summary: + // import markers from outside file, associate with map feature by feature id + // which identified in map shape file, e.g: "NY":"New York" + // markerFile: + // outside marker data url, handled as json style. + // data format: {"NY":"New York",.....} + xhr.get({ + url: markerFile, + handleAs: "json", + handle: lang.hitch(this, "_appendMarker") + }); + }, + + setDataBindingAttribute: function(/*String*/prop) { + // summary: + // sets the property name of the dataStore items to use as value (see Feature.setValue function) + // prop: + // the property + this.dataBindingAttribute = prop; + + // refresh data + if (this.dataStore) { + this._queryDataStore(); + } + }, + + setDataBindingValueFunction: function(/* function */valueFunction) { + // summary: + // sets the function that extracts values from dataStore items,to use as Feature values (see Feature.setValue function) + // prop: + // the function + this.dataBindingValueFunction = valueFunction; + + // refresh data + if (this.dataStore) { + this._queryDataStore(); + } + }, + + + + _queryDataStore: function() { + if (!this.dataBindingAttribute || (this.dataBindingAttribute.length == 0)) + return; + + var mapInstance = this; + this.dataStore.fetch({ + scope: this, + onComplete: function(items){ + this._idAttributes = mapInstance.dataStore.getIdentityAttributes({}); + arr.forEach(items, function(item) { + var id = mapInstance.dataStore.getValue(item, this._idAttributes[0]); + if(mapInstance.mapObj.features[id]){ + var val = null; + var itemVal = mapInstance.dataStore.getValue(item, mapInstance.dataBindingAttribute); + if (itemVal) { + if (this.dataBindingValueFunction) { + val = this.dataBindingValueFunction(itemVal); + } else { + if (isNaN(val)) { + // regular parse + val=number.parse(itemVal); + } else { + val = itemVal; + } + } + } + if (val) + mapInstance.mapObj.features[id].setValue(val); + } + },this); + } + }); + }, + + _onSet:function(item,attribute,oldValue,newValue){ + // look for matching feature + var id = this.dataStore.getValue(item, this._idAttributes[0]); + var feature = this.mapObj.features[id]; + if (feature && (attribute == this.dataBindingAttribute)) { + if (newValue) + feature.setValue(newValue); + else + feature.unsetValue(); + } + }, + + _onNew:function(newItem, parentItem){ + var id = this.dataStore.getValue(item, this._idAttributes[0]); + var feature = this.mapObj.features[id]; + if (feature && (attribute == this.dataBindingAttribute)) { + feature.setValue(newValue); + } + }, + + _onDelete:function(item){ + var id = item[this._idAttributes[0]]; + var feature = this.mapObj.features[id]; + if (feature) { + feature.unsetValue(); + } + }, + + setDataStore: function(/*ItemFileReadStore*/ dataStore, /*String*/ dataBindingProp){ + // summary: + // populate data for each map feature from fetched data store + // dataStore: + // the dataStore to fetch the information from + // dataBindingProp: + // sets the property name of the dataStore items to use as value + if (this.dataStore != dataStore) { + // disconnect previous listener if any + if (this._onSetListener) { + connect.disconnect(this._onSetListener); + connect.disconnect(this._onNewListener); + connect.disconnect(this._onDeleteListener); + } + + // set new dataStore + this.dataStore = dataStore; + + // install listener on new dataStore + if (dataStore) { + _onSetListener = connect.connect(this.dataStore,"onSet",this,this._onSet); + _onNewListener = connect.connect(this.dataStore,"onNew",this,this._onNew); + _onDeleteListener = connect.connect(this.dataStore,"onDelete",this,this._onDelete); + } + } + if (dataBindingProp) + this.setDataBindingAttribute(dataBindingProp); + + }, + + + + addSeries: function(/*url or Json Object*/ series){ + // summary: + // sets ranges of data values (associated with label, color) to style map data values + // series: + // array of range objects such as : [{name:'label 1', min:20, max:70, color:'#DDDDDD'},{...},...] + + if (typeof series == "object") { + this._addSeriesImpl(series); + } else { + // load series file + if (typeof series == "string" && series.length > 0) { + xhr.get({ + url: series, + handleAs: "json", + sync: true, + load: lang.hitch(this, function(content){ + this._addSeriesImpl(content.series); + }) + }); + } + } + + }, + + _addSeriesImpl: function(/*Json object*/series) { + + this.series = series; + + // refresh color scheme + for (var item in this.mapObj.features) { + var feature = this.mapObj.features[item]; + feature.setValue(feature.value); + } + }, + + + fitToMapArea: function(/*bbox: {x,y,w,h}*/mapArea,pixelMargin,animate,/* callback function */onAnimationEnd){ + // summary: + // set this component's transformation so that the specified area fits in the component (centered) + // mapArea: + // the map area that needs to fill the component + // pixelMargin: int + // a margin (in pixels) from the borders of the Map component. + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + if(!pixelMargin){ + pixelMargin = 0; + } + var width = mapArea.w, + height = mapArea.h, + containerBounds = this._getContainerBounds(), + scale = Math.min((containerBounds.w - 2 * pixelMargin) / width, + (containerBounds.h - 2 * pixelMargin) / height); + + this.setMapCenterAndScale(mapArea.x + mapArea.w / 2,mapArea.y + mapArea.h / 2,scale,animate,onAnimationEnd); + }, + + fitToMapContents: function(pixelMargin,animate,/* callback function */onAnimationEnd){ + // summary: + // set this component's transformation so that the whole map data fits in the component (centered) + // pixelMargin: int + // a margin (in pixels) from the borders of the Map component. + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + //transform map to fit container + var bbox = this.mapObj.boundBox; + this.fitToMapArea(bbox,pixelMargin,animate,onAnimationEnd); + }, + + setMapCenter: function(centerX,centerY,animate,/* callback function */onAnimationEnd) { + // summary: + // set this component's transformation so that the map is centered on the specified map coordinates + // centerX: float + // the X coordinate (in map coordinates) of the new center + // centerY: float + // the Y coordinate (in map coordinates) of the new center + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + // call setMapCenterAndScale with current map scale + var currentScale = this.getMapScale(); + this.setMapCenterAndScale(centerX,centerY,currentScale,animate,onAnimationEnd); + + }, + + _createAnimation: function(onShape,fromTransform,toTransform,/* callback function */onAnimationEnd) { + // summary: + // creates a transform animation object (between two transforms) used internally + // fromTransform: dojox.gfx.matrix.Matrix2D + // the start transformation (when animation begins) + // toTransform: dojox.gfx.matrix.Matrix2D + // the end transormation (when animation ends) + // onAnimationEnd: function + // callback function to be executed when the animation completes. + var fromDx = fromTransform.dx?fromTransform.dx:0; + var fromDy = fromTransform.dy?fromTransform.dy:0; + var toDx = toTransform.dx?toTransform.dx:0; + var toDy = toTransform.dy?toTransform.dy:0; + var fromScale = fromTransform.xx?fromTransform.xx:1.0; + var toScale = toTransform.xx?toTransform.xx:1.0; + + var anim = gfx.fx.animateTransform({ + duration: 1000, + shape: onShape, + transform: [{ + name: "translate", + start: [fromDx,fromDy], + end: [toDx,toDy] + }, + { + name: "scale", + start: [fromScale], + end: [toScale] + } + ] + }); + + //install callback + if (onAnimationEnd) { + var listener = connect.connect(anim,"onEnd",this,function(event){ + onAnimationEnd(event); + connect.disconnect(listener); + }); + } + + return anim; + }, + + + setMapCenterAndScale: function(centerX,centerY,scale, animate,/* callback function */onAnimationEnd) { + + // summary: + // set this component's transformation so that the map is centered on the specified map coordinates + // and scaled to the specified scale. + // centerX: float + // the X coordinate (in map coordinates) of the new center + // centerY: float + // the Y coordinate (in map coordinates) of the new center + // scale: float + // the scale of the map + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + + // compute matrix parameters + var bbox = this.mapObj.boundBox; + var containerBounds = this._getContainerBounds(); + var offsetX = containerBounds.w/2 - scale * (centerX - bbox.x); + var offsetY = containerBounds.h/2 - scale * (centerY - bbox.y); + var newTransform = new gfx.matrix.Matrix2D({xx: scale, yy: scale, dx:offsetX, dy:offsetY}); + + + var currentTransform = this.mapObj.getTransform(); + + // can animate only if specified AND curentTransform exists + if (!animate || !currentTransform) { + this.mapObj.setTransform(newTransform); + } else { + var anim = this._createAnimation(this.mapObj,currentTransform,newTransform,onAnimationEnd); + anim.play(); + } + }, + + getMapCenter: function() { + // summary: + // returns the map coordinates of the center of this Map component. + // returns: {x:,y:} + // the center in map coordinates + var containerBounds = this._getContainerBounds(); + return this.screenCoordsToMapCoords(containerBounds.w/2,containerBounds.h/2); + }, + + setMapScale: function(scale,animate,/* callback function */onAnimationEnd) { + // summary: + // set this component's transformation so that the map is scaled to the specified scale. + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + + // default invariant is map center + var containerBounds = this._getContainerBounds(); + var invariantMapPoint = this.screenCoordsToMapCoords(containerBounds.w/2,containerBounds.h/2); + this.setMapScaleAt(scale,invariantMapPoint.x,invariantMapPoint.y,animate,onAnimationEnd); + }, + + setMapScaleAt: function(scale,fixedMapX,fixedMapY,animate,/* callback function */onAnimationEnd) { + // summary: + // set this component's transformation so that the map is scaled to the specified scale, and the specified + // point (in map coordinates) stays fixed on this Map component + // fixedMapX: float + // the X coordinate (in map coordinates) of the fixed screen point + // fixedMapY: float + // the Y coordinate (in map coordinates) of the fixed screen point + // animate: boolean + // true if the transform change should be animated + // onAnimationEnd: function + // a callback function to be executed when the animation completes (if animate set to true). + + + var invariantMapPoint = null; + var invariantScreenPoint = null; + + invariantMapPoint = {x: fixedMapX, y: fixedMapY}; + invariantScreenPoint = this.mapCoordsToScreenCoords(invariantMapPoint.x,invariantMapPoint.y); + + // compute matrix parameters + var bbox = this.mapObj.boundBox; + var offsetX = invariantScreenPoint.x - scale * (invariantMapPoint.x - bbox.x); + var offsetY = invariantScreenPoint.y - scale * (invariantMapPoint.y - bbox.y); + var newTransform = new gfx.matrix.Matrix2D({xx: scale, yy: scale, dx:offsetX, dy:offsetY}); + + var currentTransform = this.mapObj.getTransform(); + + // can animate only if specified AND curentTransform exists + if (!animate || !currentTransform) { + this.mapObj.setTransform(newTransform); + } else { + var anim = this._createAnimation(this.mapObj,currentTransform,newTransform,onAnimationEnd); + anim.play(); + } + }, + + getMapScale: function() { + // summary: + // returns the scale of this Map component. + // returns: float + // the scale + var mat = this.mapObj.getTransform(); + var scale = mat?mat.xx:1.0; + return scale; + }, + + mapCoordsToScreenCoords: function(mapX,mapY) { + // summary: + // converts map coordinates to screen coordinates given the current transform of this Map component + // returns: {x:,y:} + // the screen coordinates correspondig to the specified map coordinates. + var matrix = this.mapObj.getTransform(); + var screenPoint = gfx.matrix.multiplyPoint(matrix, mapX, mapY); + return screenPoint; + }, + + screenCoordsToMapCoords: function(screenX, screenY) { + // summary: + // converts screen coordinates to map coordinates given the current transform of this Map component + // returns: {x:,y:} + // the map coordinates corresponding to the specified screen coordinates. + var invMatrix = gfx.matrix.invert(this.mapObj.getTransform()); + var mapPoint = gfx.matrix.multiplyPoint(invMatrix, screenX, screenY); + return mapPoint; + }, + deselectAll: function(){ + // summary: + // deselect all features of map + for(var name in this.mapObj.features){ + this.mapObj.features[name].select(false); + } + this.selectedFeature = null; + this.focused = false; + }, + + _init: function(shapeData){ + + // summary: + // inits this Map component. + + //transform map to fit container + this.mapObj.boundBox = {x: shapeData.layerExtent[0], + y: shapeData.layerExtent[1], + w: (shapeData.layerExtent[2] - shapeData.layerExtent[0]), + h: shapeData.layerExtent[3] - shapeData.layerExtent[1]}; + this.fitToMapContents(3); + + + // if there are "features", then implement them now. + arr.forEach(shapeData.featureNames, function(item){ + var featureShape = shapeData.features[item]; + featureShape.bbox.x = featureShape.bbox[0]; + featureShape.bbox.y = featureShape.bbox[1]; + featureShape.bbox.w = featureShape.bbox[2]; + featureShape.bbox.h = featureShape.bbox[3]; + var feature = new Feature(this, item, featureShape); + feature.init(); + this.mapObj.features[item] = feature; + }, this); + + + // set up a marker. + this.mapObj.marker = new Marker({}, this); + }, + _appendMarker: function(markerData){ + this.mapObj.marker = new Marker(markerData, this); + }, + _createZoomingCursor: function(){ + if(!dom.byId("mapZoomCursor")){ + var mapZoomCursor = win.doc.createElement("div"); + html.attr(mapZoomCursor,"id","mapZoomCursor"); + domClass.add(mapZoomCursor,"mapZoomIn"); + html.style(mapZoomCursor,"display","none"); + win.body().appendChild(mapZoomCursor); + } + }, + onFeatureClick: function(feature){ + }, + onFeatureOver: function(feature){ + }, + onZoomEnd:function(feature){ + } +}); +});
\ No newline at end of file diff --git a/js/dojo/dojox/geo/charting/MouseInteractionSupport.js b/js/dojo/dojox/geo/charting/MouseInteractionSupport.js new file mode 100644 index 0000000..8c1b4d3 --- /dev/null +++ b/js/dojo/dojox/geo/charting/MouseInteractionSupport.js @@ -0,0 +1,335 @@ +//>>built +define("dojox/geo/charting/MouseInteractionSupport", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/event", + "dojo/_base/connect","dojo/_base/window","dojo/_base/html","dojo/dom","dojo/_base/sniff"], + function(lang, declare, event, connect, win, html, dom, has) { + +return declare("dojox.geo.charting.MouseInteractionSupport", null, { + // summary: + // class to handle mouse interactions on a dojox.geo.charting.Map widget + // tags: + // private + + _map : null, + _mapClickLocation : null, + _screenClickLocation: null, + _mouseDragListener: null, + _mouseUpListener: null, + _mouseUpClickListener: null, + _mouseDownListener: null, + _mouseMoveListener: null, + _mouseWheelListener: null, + _currentFeature: null, + _cancelMouseClick: null, + _zoomEnabled: false, + _panEnabled: false, + _onDragStartListener: null, + _onSelectStartListener: null, + + + mouseClickThreshold: 2, + + constructor : function(/* Map */map,/*boolean*/options) { + // summary: + // Constructs a new _MouseInteractionSupport instance + // map: dojox.geo.charting.Map + // the Map widget this class provides touch navigation for. + // options: object + // to enable panning and mouse wheel zooming + this._map = map; + this._mapClickLocation = {x: 0,y: 0}; + this._screenClickLocation = {x: 0,y: 0}; + this._cancelMouseClick = false; + if (options) { + this._zoomEnabled = options.enableZoom; + this._panEnabled = options.enablePan; + if (options.mouseClickThreshold && options.mouseClickThreshold > 0) { + this.mouseClickThreshold = options.mouseClickThreshold; + } + } + }, + + setEnableZoom: function(enable){ + // summary: + // enables mouse zoom on the map + if (enable && !this._mouseWheelListener) { + // enable + var wheelEventName = !has("mozilla") ? "onmousewheel" : "DOMMouseScroll"; + this._mouseWheelListener = this._map.surface.connect(wheelEventName, this, this._mouseWheelHandler); + } else if (!enable && this._mouseWheelListener) { + // disable + connect.disconnect(this._mouseWheelListener); + this._mouseWheelListener = null; + } + this._zoomEnabled = enable; + }, + + setEnablePan: function(enable){ + // summary: + // enables mouse panning on the map + this._panEnabled = enable; + }, + + connect: function() { + // summary: + // connects this mouse support class to the Map component + + // install mouse listeners + this._mouseMoveListener = this._map.surface.connect("onmousemove", this, this._mouseMoveHandler); + this._mouseDownListener = this._map.surface.connect("onmousedown", this, this._mouseDownHandler); + + if (has("ie")) { + _onDragStartListener = connect.connect(win.doc,"ondragstart",this,event.stop); + _onSelectStartListener = connect.connect(win.doc,"onselectstart",this,event.stop); + } + + this.setEnableZoom(this._zoomEnabled); + this.setEnablePan(this._panEnabled); + }, + + disconnect: function() { + // summary: + // disconnects any installed listeners + + // store zoomPan state + var isZoom = this._zoomEnabled; + + // disable zoom (disconnects listeners..) + this.setEnableZoom(false); + + // restore value + this._zoomEnabled = isZoom; + + // disconnect remaining listeners + if (this._mouseMoveListener) { + connect.disconnect(this._mouseMoveListener); + this._mouseMoveListener = null; + connect.disconnect(this._mouseDownListener); + this._mouseDownListener = null; + } + if (this._onDragStartListener) { + connect.disconnect(this._onDragStartListener); + this._onDragStartListener = null; + connect.disconnect(this._onSelectStartListener); + this._onSelectStartListener = null; + } + + }, + + _mouseClickHandler: function(mouseEvent) { + // summary: + // action performed on the map when a mouse click was performed + // mouseEvent: the mouse event + // tags: + // private + + var feature = this._getFeatureFromMouseEvent(mouseEvent); + + if (feature) { + // call feature handler + feature._onclickHandler(mouseEvent); + } else { + // unselect all + for (var name in this._map.mapObj.features){ + this._map.mapObj.features[name].select(false); + } + this._map.onFeatureClick(null); + } + + }, + + _mouseDownHandler: function(mouseEvent){ + // summary: + // action performed on the map when a mouse down was performed + // mouseEvent: the mouse event + // tags: + // private + + + event.stop(mouseEvent); + + this._map.focused = true; + // set various status parameters + this._cancelMouseClick = false; + this._screenClickLocation.x = mouseEvent.pageX; + this._screenClickLocation.y = mouseEvent.pageY; + + // store map location where mouse down occurred + var containerBounds = this._map._getContainerBounds(); + var offX = mouseEvent.pageX - containerBounds.x, + offY = mouseEvent.pageY - containerBounds.y; + var mapPoint = this._map.screenCoordsToMapCoords(offX,offY); + this._mapClickLocation.x = mapPoint.x; + this._mapClickLocation.y = mapPoint.y; + + // install drag listener if pan is enabled + if (!has("ie")) { + this._mouseDragListener = connect.connect(win.doc,"onmousemove",this,this._mouseDragHandler); + this._mouseUpClickListener = this._map.surface.connect("onmouseup", this, this._mouseUpClickHandler); + this._mouseUpListener = connect.connect(win.doc,"onmouseup",this, this._mouseUpHandler); + } else { + var node = dom.byId(this._map.container); + this._mouseDragListener = connect.connect(node,"onmousemove",this,this._mouseDragHandler); + this._mouseUpClickListener = this._map.surface.connect("onmouseup", this, this._mouseUpClickHandler); + this._mouseUpListener = this._map.surface.connect("onmouseup", this, this._mouseUpHandler); + this._map.surface.rawNode.setCapture(); + } + }, + + _mouseUpClickHandler: function(mouseEvent) { + + if (!this._cancelMouseClick) { + // execute mouse click handler + this._mouseClickHandler(mouseEvent); + } + this._cancelMouseClick = false; + + }, + + _mouseUpHandler: function(mouseEvent) { + // summary: + // action performed on the map when a mouse up was performed + // mouseEvent: the mouse event + // tags: + // private + + event.stop(mouseEvent); + + this._map.mapObj.marker._needTooltipRefresh = true; + + // disconnect listeners + if (this._mouseDragListener) { + connect.disconnect(this._mouseDragListener); + this._mouseDragListener = null; + } + if (this._mouseUpClickListener) { + connect.disconnect(this._mouseUpClickListener); + this._mouseUpClickListener = null; + } + if (this._mouseUpListener) { + connect.disconnect(this._mouseUpListener); + this._mouseUpListener = null; + } + + if (has("ie")) { + this._map.surface.rawNode.releaseCapture(); + } + }, + + _getFeatureFromMouseEvent: function(mouseEvent) { + // summary: + // utility function to return the feature located at this mouse event location + // mouseEvent: the mouse event + // returns: dojox.geo.charting.Feature + // the feature found if any, null otherwise. + // tags: + // private + var feature = null; + if (mouseEvent.gfxTarget && mouseEvent.gfxTarget.getParent) { + feature = this._map.mapObj.features[mouseEvent.gfxTarget.getParent().id]; + } + return feature; + }, + + _mouseMoveHandler: function(mouseEvent) { + // summary: + // action performed on the map when a mouse move was performed + // mouseEvent: the mouse event + // tags: + // private + + + // do nothing more if dragging + if (this._mouseDragListener && this._panEnabled) { + return; + } + + // hover and highlight + var feature = this._getFeatureFromMouseEvent(mouseEvent); + + // set/unset highlight + if (feature != this._currentFeature) { + if (this._currentFeature) { + // mouse leaving component + this._currentFeature._onmouseoutHandler(); + } + this._currentFeature = feature; + + if (feature) + feature._onmouseoverHandler(); + } + + if (feature) + feature._onmousemoveHandler(mouseEvent); + }, + + _mouseDragHandler: function(mouseEvent){ + // summary: + // action performed on the map when a mouse drag was performed + // mouseEvent: the mouse event + // tags: + // private + + // prevent browser interaction + event.stop(mouseEvent); + + + // find out if this the movement discards the "mouse click" gesture + var dx = Math.abs(mouseEvent.pageX - this._screenClickLocation.x); + var dy = Math.abs(mouseEvent.pageY - this._screenClickLocation.y); + if (!this._cancelMouseClick && (dx > this.mouseClickThreshold || dy > this.mouseClickThreshold)) { + // cancel mouse click + this._cancelMouseClick = true; + if (this._panEnabled) { + this._map.mapObj.marker.hide(); + } + } + + if (!this._panEnabled) + return; + + var cBounds = this._map._getContainerBounds(); + var offX = mouseEvent.pageX - cBounds.x, + offY = mouseEvent.pageY - cBounds.y; + + // compute map offset + var mapPoint = this._map.screenCoordsToMapCoords(offX,offY); + var mapOffsetX = mapPoint.x - this._mapClickLocation.x; + var mapOffsetY = mapPoint.y - this._mapClickLocation.y; + + // adjust map center + var currentMapCenter = this._map.getMapCenter(); + this._map.setMapCenter(currentMapCenter.x - mapOffsetX, currentMapCenter.y - mapOffsetY); + + }, + + _mouseWheelHandler: function(mouseEvent) { + // summary: + // action performed on the map when a mouse wheel up/down was performed + // mouseEvent: the mouse event + // tags: + // private + + + // prevent browser interaction + event.stop(mouseEvent); + + // hide tooltip + this._map.mapObj.marker.hide(); + + // event coords within component + var containerBounds = this._map._getContainerBounds(); + var offX = mouseEvent.pageX - containerBounds.x, + offY = mouseEvent.pageY - containerBounds.y; + + // current map point before zooming + var invariantMapPoint = this._map.screenCoordsToMapCoords(offX,offY); + + // zoom increment power + var power = mouseEvent[(has("mozilla") ? "detail" : "wheelDelta")] / (has("mozilla") ? - 3 : 120) ; + var scaleFactor = Math.pow(1.2,power); + this._map.setMapScaleAt(this._map.getMapScale()*scaleFactor ,invariantMapPoint.x,invariantMapPoint.y,false); + this._map.mapObj.marker._needTooltipRefresh = true; + + } +}); +}); diff --git a/js/dojo/dojox/geo/charting/TouchInteractionSupport.js b/js/dojo/dojox/geo/charting/TouchInteractionSupport.js new file mode 100644 index 0000000..6e2b7cd --- /dev/null +++ b/js/dojo/dojox/geo/charting/TouchInteractionSupport.js @@ -0,0 +1,313 @@ +//>>built +define("dojox/geo/charting/TouchInteractionSupport", ["dojo/_base/lang","dojo/_base/declare","dojo/_base/event", "dojo/_base/connect","dojo/_base/window"], + function(lang,declare,event,connect,win) { + +return declare("dojox.geo.charting.TouchInteractionSupport",null, { + // summary: + // class to handle touch interactions on a dojox.geo.charting.Map widget + // tags: + // private + + _map : null, + _centerTouchLocation : null, + _touchMoveListener: null, + _touchEndListener: null, + _touchEndTapListener: null, + _touchStartListener: null, + _initialFingerSpacing: null, + _initialScale: null, + _tapCount: null, + _tapThreshold: null, + _lastTap: null, + _doubleTapPerformed:false, + _oneFingerTouch:false, + _tapCancel:false, + + constructor : function(/* dojox.geo.charting.Map */map,options) { + // summary: + // Constructs a new _TouchInteractionSupport instance + // map: dojox.geo.charting.Map + // the Map widget this class provides touch navigation for. + this._map = map; + this._centerTouchLocation = {x: 0,y: 0}; + + this._tapCount = 0; + this._lastTap = {x: 0,y: 0}; + this._tapThreshold = 100; // square distance in pixels + }, + + connect: function() { + // summary: + // install touch listeners + _touchStartListener = this._map.surface.connect("touchstart", this, this._touchStartHandler); + }, + + disconnect: function() { + // summary: + // disconnects any installed listeners. Must be called only when disposing of this instance + if (this._touchStartListener) { + connect.disconnect(this._touchStartListener); + this._touchStartListener = null; + } + }, + + _getTouchBarycenter: function(touchEvent) { + // summary: + // returns the midpoint of the two first fingers (or the first finger location if only one) + // touchEvent: a touch event + // returns: dojox.gfx.Point + // the midpoint + // tags: + // private + var touches = touchEvent.touches; + var firstTouch = touches[0]; + var secondTouch = null; + if (touches.length > 1) { + secondTouch = touches[1]; + } else { + secondTouch = touches[0]; + } + var containerBounds = this._map._getContainerBounds(); + var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - containerBounds.x; + var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - containerBounds.y; + return {x: middleX,y: middleY}; + }, + + _getFingerSpacing: function(touchEvent) { + // summary: + // computes the distance between the first two fingers + // touchEvent: a touch event + // returns: float + // a distance. -1 if less that 2 fingers + // tags: + // private + var touches = touchEvent.touches; + var spacing = -1; + if (touches.length >= 2) { + var dx = (touches[1].pageX - touches[0].pageX); + var dy = (touches[1].pageY - touches[0].pageY); + spacing = Math.sqrt(dx*dx + dy*dy); + } + return spacing; + }, + + _isDoubleTap: function(touchEvent) { + // summary: + // checks whether the specified touchStart event is a double tap + // (i.e. follows closely a previous touchStart at approximately the same location) + // touchEvent: a touch event + // returns: boolean + // true if this event is considered a double tap + // tags: + // private + var isDoubleTap = false; + var touches = touchEvent.touches; + if ((this._tapCount > 0) && touches.length == 1) { + // test distance from last tap + var dx = (touches[0].pageX - this._lastTap.x); + var dy = (touches[0].pageY - this._lastTap.y); + var distance = dx*dx + dy*dy; + if (distance < this._tapThreshold) { + isDoubleTap = true; + } else { + this._tapCount = 0; + } + } + this._tapCount++; + this._lastTap.x = touches[0].pageX; + this._lastTap.y = touches[0].pageY; + setTimeout(lang.hitch(this,function() { + this._tapCount = 0;}),300); + return isDoubleTap; + }, + + _doubleTapHandler: function(touchEvent) { + // summary: + // action performed on the map when a double tap was triggered + // touchEvent: a touch event + // tags: + // private + var feature = this._getFeatureFromTouchEvent(touchEvent); + if (feature) { + this._map.fitToMapArea(feature._bbox, 15, true); + } else { + // perform a basic 2x zoom on touch + var touches = touchEvent.touches; + var containerBounds = this._map._getContainerBounds(); + var offX = touches[0].pageX - containerBounds.x; + var offY = touches[0].pageY - containerBounds.y; + // clicked map point before zooming + var mapPoint = this._map.screenCoordsToMapCoords(offX,offY); + // zoom increment power + this._map.setMapCenterAndScale(mapPoint.x, mapPoint.y,this._map.getMapScale()*2,true); + } + }, + + _getFeatureFromTouchEvent: function(touchEvent) { + // summary: + // utility function to return the feature located at this touch event location + // touchEvent: a touch event + // returns: dojox.geo.charting.Feature + // the feature found if any, null otherwise. + // tags: + // private + var feature = null; + if (touchEvent.gfxTarget && touchEvent.gfxTarget.getParent) { + feature = this._map.mapObj.features[touchEvent.gfxTarget.getParent().id]; + } + return feature; + }, + + _touchStartHandler: function(touchEvent){ + // summary: + // action performed on the map when a touch start was triggered + // touchEvent: a touch event + // tags: + // private + event.stop(touchEvent); + this._oneFingerTouch = (touchEvent.touches.length == 1); + this._tapCancel = !this._oneFingerTouch; + // test double tap + this._doubleTapPerformed = false; + if (this._isDoubleTap(touchEvent)) { + //console.log("double tap recognized"); + this._doubleTapHandler(touchEvent); + this._doubleTapPerformed = true; + return; + } + // compute map midpoint between fingers + var middlePoint = this._getTouchBarycenter(touchEvent); + var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y); + this._centerTouchLocation.x = mapPoint.x; + this._centerTouchLocation.y = mapPoint.y; + // store initial finger spacing to compute zoom later + this._initialFingerSpacing = this._getFingerSpacing(touchEvent); + // store initial map scale + this._initialScale = this._map.getMapScale(); + // install touch move and up listeners (if not done by other fingers before) + if (!this._touchMoveListener) + this._touchMoveListener = connect.connect(win.global,"touchmove",this,this._touchMoveHandler); + if (!this._touchEndTapListener) + this._touchEndTapListener = this._map.surface.connect("touchend", this, this._touchEndTapHandler); + if (!this._touchEndListener) + this._touchEndListener = connect.connect(win.global,"touchend",this, this._touchEndHandler); + }, + + _touchEndTapHandler: function(touchEvent) { + // summary: + // action performed on the map when a tap was triggered + // touchEvent: a touch event + // tags: + // private + var touches = touchEvent.touches; + if (touches.length == 0) { + + // test potential tap ? + if (this._oneFingerTouch && !this._tapCancel) { + this._oneFingerTouch = false; + setTimeout(lang.hitch(this,function() { + // wait to check if double tap + // perform test for single tap + //console.log("double tap was performed ? " + this._doubleTapPerformed); + if (!this._doubleTapPerformed) { + // test distance from last tap + var dx = (touchEvent.changedTouches[0].pageX - this._lastTap.x); + var dy = (touchEvent.changedTouches[0].pageY - this._lastTap.y); + var distance = dx*dx + dy*dy; + if (distance < this._tapThreshold) { + // single tap ok + this._singleTapHandler(touchEvent); + } + } + }),350); + } + this._tapCancel = false; + + } + }, + + _touchEndHandler: function(touchEvent) { + // summary: + // action performed on the map when a touch end was triggered + // touchEvent: a touch event + // tags: + // private + event.stop(touchEvent); + var touches = touchEvent.touches; + if (touches.length == 0) { + // disconnect listeners only when all fingers are up + if (this._touchMoveListener) { + connect.disconnect(this._touchMoveListener); + this._touchMoveListener = null; + } + if (this._touchEndListener) { + connect.disconnect(this._touchEndListener); + this._touchEndListener = null; + } + } else { + // recompute touch center + var middlePoint = this._getTouchBarycenter(touchEvent); + var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y); + this._centerTouchLocation.x = mapPoint.x; + this._centerTouchLocation.y = mapPoint.y; + } + }, + + _singleTapHandler: function(touchEvent) { + // summary: + // action performed on the map when a single tap was triggered + // touchEvent: a touch event + // tags: + // private + var feature = this._getFeatureFromTouchEvent(touchEvent); + if (feature) { + // call feature handler + feature._onclickHandler(touchEvent); + } else { + // unselect all + for (var name in this._map.mapObj.features){ + this._map.mapObj.features[name].select(false); + } + this._map.onFeatureClick(null); + } + }, + + _touchMoveHandler: function(touchEvent){ + // summary: + // action performed on the map when a touch move was triggered + // touchEvent: a touch event + // tags: + // private + + // prevent browser interaction + event.stop(touchEvent); + + // cancel tap if moved too far from first touch location + if (!this._tapCancel) { + var dx = (touchEvent.touches[0].pageX - this._lastTap.x), + dy = (touchEvent.touches[0].pageY - this._lastTap.y); + var distance = dx*dx + dy*dy; + if (distance > this._tapThreshold) { + this._tapCancel = true; + } + } + var middlePoint = this._getTouchBarycenter(touchEvent); + // compute map offset + var mapPoint = this._map.screenCoordsToMapCoords(middlePoint.x,middlePoint.y), + mapOffsetX = mapPoint.x - this._centerTouchLocation.x, + mapOffsetY = mapPoint.y - this._centerTouchLocation.y; + // compute scale factor + var scaleFactor = 1; + var touches = touchEvent.touches; + if (touches.length >= 2) { + var fingerSpacing = this._getFingerSpacing(touchEvent); + scaleFactor = fingerSpacing / this._initialFingerSpacing; + // scale map + this._map.setMapScale(this._initialScale*scaleFactor); + } + // adjust map center on barycentre + var currentMapCenter = this._map.getMapCenter(); + this._map.setMapCenter(currentMapCenter.x - mapOffsetX, currentMapCenter.y - mapOffsetY); + } +}); +}); diff --git a/js/dojo/dojox/geo/charting/_Marker.js b/js/dojo/dojox/geo/charting/_Marker.js new file mode 100644 index 0000000..28bf227 --- /dev/null +++ b/js/dojo/dojox/geo/charting/_Marker.js @@ -0,0 +1,66 @@ +//>>built + +define("dojox/geo/charting/_Marker", ["dojo/_base/lang","dojo/_base/array", "dojo/_base/declare","dojo/_base/sniff","./_base"], + function(lang, arr, declare, has) { +return declare("dojox.geo.charting._Marker", null, { + + _needTooltipRefresh: null, + _map: null, + + constructor: function(markerData, map){ + this._map = map; + var mapObj = map.mapObj; + this.features = mapObj.features; + this.markerData = markerData; + _needTooltipRefresh = false; + }, + + show: function(featureId,evt){ + this.currentFeature = this.features[featureId]; + if (this._map.showTooltips && this.currentFeature) { + this.markerText = this.currentFeature.markerText || this.markerData[featureId] || featureId; + dojox.geo.charting.showTooltip(this.markerText, this.currentFeature.shape, ["before"]); + } + this._needTooltipRefresh = false; + }, + + hide: function(){ + if (this._map.showTooltips && this.currentFeature) + dojox.geo.charting.hideTooltip(this.currentFeature.shape); + this._needTooltipRefresh = false; + }, + + _getGroupBoundingBox: function(group){ + var shapes = group.children; + var feature = shapes[0]; + var bbox = feature.getBoundingBox(); + this._arround = lang.clone(bbox); + arr.forEach(shapes, function(item){ + var _bbox = item.getBoundingBox(); + this._arround.x = Math.min(this._arround.x, _bbox.x); + this._arround.y = Math.min(this._arround.y, _bbox.y); + },this); + }, + + _toWindowCoords: function(arround, coords, containerSize){ + var toLeft = (arround.x - this.topLeft[0]) * this.scale; + var toTop = (arround.y - this.topLeft[1]) * this.scale + if (has("ff") == 3.5) { + arround.x = coords.x; + arround.y = coords.y; + } + else if (has("chrome")) { + arround.x = containerSize.x + toLeft; + arround.y = containerSize.y + toTop; + } + else { + arround.x = coords.x + toLeft; + arround.y = coords.y + toTop; + } + arround.width = (this.currentFeature._bbox[2]) * this.scale; + arround.height = (this.currentFeature._bbox[3]) * this.scale; + arround.x += arround.width / 6; + arround.y += arround.height / 4; + } +}); +}); diff --git a/js/dojo/dojox/geo/charting/_base.js b/js/dojo/dojox/geo/charting/_base.js new file mode 100644 index 0000000..8a461da --- /dev/null +++ b/js/dojo/dojox/geo/charting/_base.js @@ -0,0 +1,55 @@ +//>>built +define("dojox/geo/charting/_base", ["dojo/_base/lang","dojo/_base/array","../../main", "dojo/_base/html","dojo/dom-geometry", + "dojox/gfx/matrix","dijit/Tooltip","dojo/_base/NodeList","dojo/NodeList-traverse"], + function(lang, arr, dojox, html, domGeom, matrix, Tooltip, NodeList, NodeListTraverse) { + var dgc = lang.getObject("geo.charting", true, dojox); + + dgc.showTooltip = function(/*String*/innerHTML, /*dojox.gfx.shape*/ gfxObject, /*String[]?*/ positions){ + var arroundNode = dgc._normalizeArround(gfxObject); + return Tooltip.show(innerHTML, arroundNode, positions); + }; + + dgc.hideTooltip = function( /*dojox.gfx.shape*/gfxObject){ + return Tooltip.hide(gfxObject); + }; + + dgc._normalizeArround = function(gfxObject){ + var bbox = dgc._getRealBBox(gfxObject); + //var bbox = gfxObject.getBoundingBox(); + //get the real screen coords for gfx object + var realMatrix = gfxObject._getRealMatrix() || {xx:1,xy:0,yx:0,yy:1,dx:0,dy:0}; + var point = matrix.multiplyPoint(realMatrix, bbox.x, bbox.y); + var gfxDomContainer = dgc._getGfxContainer(gfxObject); + gfxObject.x = domGeom.position(gfxDomContainer,true).x + point.x, + gfxObject.y = domGeom.position(gfxDomContainer,true).y + point.y, + gfxObject.w = bbox.width * realMatrix.xx, + gfxObject.h = bbox.height * realMatrix.yy + return gfxObject; + }; + + dgc._getGfxContainer = function(gfxObject){ + if (gfxObject.surface) { + return (new NodeList(gfxObject.surface.rawNode)).parents("div")[0]; + } else { + return (new NodeList(gfxObject.rawNode)).parents("div")[0]; + } + }; + + dgc._getRealBBox = function(gfxObject){ + var bboxObject = gfxObject.getBoundingBox(); + if(!bboxObject){//the gfx object is group + var shapes = gfxObject.children; + bboxObject = lang.clone(dgc._getRealBBox(shapes[0])); + arr.forEach(shapes, function(item){ + var nextBBox = dgc._getRealBBox(item); + bboxObject.x = Math.min(bboxObject.x, nextBBox.x); + bboxObject.y = Math.min(bboxObject.y, nextBBox.y); + bboxObject.endX = Math.max(bboxObject.x + bboxObject.width, nextBBox.x + nextBBox.width); + bboxObject.endY = Math.max(bboxObject.y + bboxObject.height, nextBBox.y + nextBBox.height); + }); + bboxObject.width = bboxObject.endX - bboxObject.x; + bboxObject.height = bboxObject.endY - bboxObject.y; + } + return bboxObject; + }; +}); diff --git a/js/dojo/dojox/geo/charting/resources/Map.css b/js/dojo/dojox/geo/charting/resources/Map.css new file mode 100644 index 0000000..48409ee --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/Map.css @@ -0,0 +1,38 @@ +.tundra .dijitTooltipAbove .dijitTooltipConnector, +.tundra .dijitTooltipLeft .dijitTooltipConnector, +.tundra .dijitTooltipBottom .dijitTooltipConnector, +.tundra .dijitTooltipRight.dijitTooltipConnector, +.dj_ie .tundra .dijitTooltipAbove .dijitTooltipConnector +.dj_ie .tundra .dijitTooltipLeft .dijitTooltipConnector, +.dj_ie .tundra .dijitTooltipBottom .dijitTooltipConnector, +.dj_ie .tundra .dijitTooltipRight .dijitTooltipConnector +{ + display: none; +} + +.dojoxLegendNode { + border: 1px solid #ccc; + margin: 5px 10px 5px 10px; + padding: 3px; + float:right; +} + +.dojoxLegendText { + vertical-align: text-top; + padding-right: 10px +} + +.mapZoomIn { + width: 21px; + height: 21px; + background: url(img/zoomin.png) no-repeat; + position: absolute; +} + +.mapZoomOut { + width: 21px; + height: 21px; + background: url(img/zoomout.png) no-repeat; + position: absolute; +} + diff --git a/js/dojo/dojox/geo/charting/resources/data/NOTICES b/js/dojo/dojox/geo/charting/resources/data/NOTICES new file mode 100644 index 0000000..5d62e07 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/data/NOTICES @@ -0,0 +1,63 @@ +Mapping data in this directory was obtained from the United States Geological Survey (USGS) at http://nationalatlas.gov/atlasftp.html. +Data and information authored or produced by the USGS are in the public domain: http://nationalatlas.gov/policies.html. +The following notices and information are provided by the USGS: +----------------------------------------------------------------------------------------------------------------------------------------------- + + + Are there legal restrictions on access or use of the data? + + Access_Constraints: None + Use_Constraints: + None. Acknowledgment of the National Atlas of the United States + of America and (or) the U.S. Geological Survey would be + appreciated in products derived from these data. + + 1. Who distributes the data set? (Distributor 1 of 1) + + Earth Science Information Center, U.S. Geological Survey + 507 National Center + Reston, VA 20192 + + 1-888-ASK-USGS (1-888-275-8747) (voice) + + + Contact_Instructions: + In addition to the address above there are other ESIC offices throughout the + country. A full list of these offices is at + <http://mapping.usgs.gov/esic/esic_index.html>. + + 2. What's the catalog number I need to order this data set? + + 3. What legal disclaimers am I supposed to read? + + Although these data have been processed successfully on a computer system + at the U.S. Geological Survey, no warranty expressed or implied is made by the U.S. Geological Survey regarding the utility of the data on any other system, nor shall the act of distribution constitute any such warranty. No responsibility is assumed by the U.S. Geological Survey in the use of these data. + + 4. How can I download or order the data? + + Availability in digital form: + + Data format: ESRI Shapefile + Network links: <http://nationalatlas.gov/atlasftp.html> + + Data format: SDTS + Network links: <http://nationalatlas.gov/atlasftp.html> + + + Cost to order the data: + + There is no charge for the online option. For National Atlas files ordered on CD-ROM + there is a base price of $45.00 per disc, a handling fee of $5.00, and a per-file + charge based on file size. The charge for files less than 10 megabytes in size is + $1.00. The charge for files that range in size from 10 to 150 megabytes is $7.50. The + charge for files of 150 megabytes or larger is $15.00. The charge is $7.50 for the + Major Roads of the United States data set. + + + Special instructions: + + To order files on CD-ROM, please see <http://nationalatlas.gov/atlasftp.html#q12>. + +--------------------------------------------------------------------------------------------------------------------- + +This Shapefile data is then reformatted into a simple JSON format diff --git a/js/dojo/dojox/geo/charting/resources/data/USStates.json b/js/dojo/dojox/geo/charting/resources/data/USStates.json new file mode 100644 index 0000000..7014802 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/data/USStates.json @@ -0,0 +1,262 @@ +{ + "layerExtent":[0.0, 0.0, 8036.4007820436855, 5262.578216533747], + "layerExtentLL":[-124.7342529296875, 23.725082397460938, 57.780250549316406, 25.66031265258789], + "featureNames":["RI", "VT", "HI", "ME", "VA", "MI", "DE", "ID", "IA", "MD", "MA", "AR", "IL", "UT", "IN", "MN", "AZ", "MO", "MT", "MS", "NH", "NJ", "NM", "AK", "TX", "AL", "NC", "ND", "NE", "NY", "GA", "NV", "TN", "CA", "OK", "OH", "WY", "FL", "SD", "SC", "CT", "WV", "DC", "WI", "KY", "KS", "OR", "LA", "WA", "CO", "PA"], + "features":{ + "RI":{ + "shape":[[7641, 1436, 7651, 1437, 7661, 1467, 7661, 1467, 7653, 1478, 7641, 1436], [7541, 1398, 7559, 1392, 7598, 1380, 7615, 1420, 7635, 1430, 7635, 1431, 7627, 1445, 7626, 1427, 7615, 1429, 7607, 1410, 7618, 1435, 7606, 1444, 7617, 1460, 7618, 1506, 7612, 1496, 7568, 1527, 7568, 1526, 7541, 1398], [7633, 1474, 7639, 1442, 7645, 1476, 7631, 1485, 7633, 1474]], + "center":[7585, 1442], + "bbox":[7541.023426203894, 1379.9911631012965, 120.38955278578123, 146.89851936205946] + }, + "VT":{ + "shape":[7427, 828, 7434, 848, 7424, 882, 7445, 909, 7444, 926, 7390, 984, 7404, 1022, 7383, 1128, 7402, 1236, 7394, 1266, 7414, 1289, 7393, 1294, 7309, 1313, 7302, 1314, 7267, 1166, 7255, 1151, 7239, 1162, 7241, 1126, 7216, 1076, 7218, 999, 7196, 969, 7184, 893, 7202, 888, 7427, 828], + "center":[7317, 1057], + "bbox":[7183.629180866541, 827.7125085121527, 261.2952454831775, 486.6287466336828] + }, + "HI":{ + "shape":[[2254, 4585, 2254, 4606, 2228, 4628, 2227, 4605, 2254, 4585], [2319, 4564, 2350, 4551, 2385, 4559, 2393, 4577, 2383, 4610, 2361, 4625, 2334, 4617, 2305, 4588, 2319, 4564], [2597, 4694, 2619, 4684, 2634, 4731, 2654, 4734, 2664, 4766, 2634, 4772, 2610, 4758, 2611, 4743, 2600, 4747, 2608, 4758, 2584, 4760, 2558, 4702, 2597, 4694], [2790, 4803, 2827, 4807, 2796, 4829, 2719, 4809, 2731, 4787, 2790, 4803], [2835, 4839, 2851, 4839, 2864, 4864, 2908, 4860, 2948, 4903, 2921, 4921, 2870, 4926, 2863, 4884, 2827, 4864, 2835, 4839], [2769, 4849, 2800, 4865, 2797, 4885, 2774, 4888, 2761, 4856, 2769, 4849], [2832, 4923, 2848, 4934, 2822, 4937, 2832, 4923], [2968, 4993, 3088, 5070, 3095, 5110, 3109, 5108, 3113, 5129, 3143, 5154, 3102, 5189, 3011, 5222, 2976, 5263, 2938, 5226, 2946, 5175, 2920, 5098, 2963, 5053, 2954, 5008, 2968, 4993]], + "center":[2703, 4833], + "bbox":[2226.7877701712587, 4551.091358635575, 915.9598550544888, 711.4868578981723] + }, + "ME":{ + "shape":[7610, 288, 7625, 320, 7647, 327, 7714, 269, 7794, 304, 7867, 514, 7872, 558, 7925, 560, 7935, 577, 7924, 583, 7942, 600, 7938, 620, 7951, 635, 7967, 644, 7989, 630, 8016, 661, 8002, 670, 8009, 678, 7997, 675, 8010, 692, 8012, 675, 8023, 684, 8018, 671, 8036, 683, 8015, 725, 7984, 720, 7990, 744, 7983, 731, 7983, 745, 7967, 737, 7971, 762, 7958, 759, 7952, 775, 7939, 756, 7937, 801, 7931, 783, 7932, 800, 7922, 784, 7916, 789, 7929, 803, 7920, 819, 7884, 779, 7876, 786, 7891, 801, 7874, 793, 7886, 804, 7872, 801, 7869, 821, 7862, 803, 7853, 807, 7864, 826, 7857, 814, 7846, 826, 7865, 857, 7819, 854, 7824, 832, 7838, 837, 7824, 821, 7816, 841, 7818, 807, 7801, 800, 7800, 786, 7813, 825, 7791, 840, 7802, 856, 7794, 904, 7803, 904, 7790, 940, 7780, 939, 7789, 913, 7775, 943, 7759, 920, 7749, 972, 7741, 935, 7744, 978, 7731, 971, 7729, 937, 7717, 970, 7725, 985, 7729, 976, 7726, 996, 7706, 936, 7698, 960, 7705, 956, 7724, 999, 7717, 1008, 7706, 976, 7697, 1010, 7698, 982, 7693, 1010, 7696, 983, 7677, 993, 7662, 1036, 7678, 1048, 7660, 1054, 7657, 1069, 7666, 1075, 7643, 1107, 7641, 1157, 7617, 1153, 7616, 1152, 7613, 1135, 7581, 1111, 7468, 758, 7469, 757, 7483, 747, 7500, 764, 7499, 725, 7522, 727, 7504, 706, 7544, 619, 7524, 564, 7542, 513, 7534, 459, 7590, 287, 7610, 288], + "center":[7710, 682], + "bbox":[7467.685076331587, 269.1289575024286, 568.7157057120985, 887.6751354524711] + }, + "VA":{ + "shape":[[7177, 2288, 7216, 2271, 7216, 2271, 7186, 2362, 7195, 2366, 7166, 2410, 7166, 2458, 7149, 2420, 7162, 2388, 7151, 2391, 7152, 2377, 7165, 2373, 7151, 2366, 7172, 2341, 7161, 2341, 7171, 2335, 7160, 2328, 7182, 2314, 7176, 2301, 7163, 2305, 7177, 2288], [6817, 2100, 6818, 2100, 6841, 2099, 6860, 2110, 6855, 2132, 6921, 2156, 6921, 2156, 6928, 2160, 6929, 2161, 6940, 2194, 6923, 2201, 6931, 2212, 6914, 2210, 6913, 2262, 6903, 2256, 6919, 2269, 6907, 2273, 6953, 2253, 6967, 2288, 7003, 2288, 7017, 2305, 7013, 2287, 7027, 2287, 7045, 2303, 7039, 2313, 7055, 2309, 7054, 2322, 7059, 2311, 7092, 2323, 7090, 2339, 7063, 2332, 7091, 2345, 7083, 2366, 7093, 2376, 7070, 2375, 7073, 2359, 7057, 2358, 7063, 2379, 7048, 2354, 7022, 2349, 7021, 2336, 7002, 2334, 6984, 2310, 6938, 2298, 6982, 2312, 7049, 2379, 7098, 2387, 7064, 2396, 7109, 2403, 7113, 2418, 7111, 2435, 7098, 2414, 7097, 2424, 7080, 2411, 7090, 2426, 7075, 2420, 7078, 2430, 7091, 2432, 7079, 2435, 7097, 2442, 7080, 2452, 7013, 2401, 6989, 2408, 7021, 2406, 7060, 2447, 7098, 2456, 7093, 2470, 7118, 2469, 7104, 2473, 7110, 2485, 7122, 2476, 7104, 2503, 7062, 2463, 7026, 2465, 7021, 2450, 7018, 2468, 7001, 2456, 6959, 2466, 7003, 2459, 7004, 2474, 7050, 2479, 7055, 2468, 7064, 2496, 7094, 2506, 7090, 2539, 7113, 2509, 7113, 2530, 7136, 2521, 7118, 2499, 7155, 2502, 7153, 2513, 7167, 2496, 7204, 2560, 7205, 2562, 7201, 2563, 7202, 2562, 7185, 2533, 7186, 2565, 7187, 2566, 7183, 2567, 7182, 2566, 7173, 2559, 7179, 2567, 7179, 2568, 6333, 2723, 6319, 2725, 6017, 2767, 6012, 2768, 6091, 2729, 6098, 2707, 6127, 2694, 6144, 2648, 6230, 2571, 6247, 2552, 6258, 2584, 6301, 2608, 6344, 2575, 6368, 2591, 6415, 2567, 6418, 2545, 6433, 2553, 6467, 2527, 6479, 2534, 6500, 2512, 6493, 2505, 6507, 2491, 6492, 2481, 6550, 2358, 6560, 2293, 6621, 2315, 6647, 2227, 6670, 2239, 6723, 2150, 6720, 2092, 6809, 2139, 6817, 2101, 6817, 2100]], + "center":[6750, 2455], + "bbox":[6012.441825481979, 2091.8790360725766, 1203.6208629785642, 675.6390679572082] + }, + "MI":{ + "shape":[[5098, 797, 5138, 760, 5201, 760, 5169, 771, 5115, 855, 5107, 841, 5115, 813, 5106, 835, 5083, 821, 5098, 797], [5071, 647, 5077, 644, 5024, 682, 5036, 684, 4995, 694, 5003, 677, 5095, 620, 5071, 647], [5296, 933, 5311, 924, 5362, 943, 5421, 886, 5578, 848, 5574, 904, 5628, 898, 5637, 911, 5670, 891, 5694, 937, 5683, 947, 5717, 955, 5713, 963, 5739, 981, 5654, 996, 5635, 982, 5627, 1025, 5589, 996, 5520, 987, 5503, 1014, 5421, 1028, 5412, 1058, 5384, 1077, 5382, 1099, 5366, 1086, 5386, 1065, 5386, 1043, 5354, 1051, 5349, 1079, 5333, 1091, 5328, 1042, 5319, 1084, 5302, 1099, 5256, 1208, 5255, 1208, 5236, 1192, 5244, 1157, 5213, 1163, 5225, 1135, 5217, 1122, 5222, 1098, 5173, 1078, 5176, 1057, 4898, 996, 4883, 965, 4855, 954, 4856, 954, 4932, 902, 4986, 891, 5042, 856, 5080, 810, 5112, 853, 5110, 899, 5152, 854, 5134, 885, 5160, 863, 5197, 865, 5228, 884, 5264, 934, 5296, 933], [5692, 916, 5674, 894, 5684, 881, 5695, 883, 5688, 904, 5701, 920, 5692, 916], [5703, 935, 5697, 942, 5688, 923, 5703, 935], [5754, 974, 5771, 954, 5795, 973, 5787, 989, 5741, 984, 5754, 974], [5651, 1025, 5682, 1031, 5666, 1041, 5651, 1025], [5532, 1243, 5544, 1203, 5551, 1205, 5541, 1245, 5560, 1215, 5557, 1143, 5615, 1113, 5585, 1089, 5605, 1052, 5595, 1044, 5632, 1034, 5671, 1056, 5704, 1054, 5725, 1079, 5809, 1094, 5826, 1109, 5849, 1154, 5831, 1146, 5822, 1161, 5846, 1182, 5856, 1214, 5858, 1289, 5833, 1305, 5832, 1346, 5796, 1363, 5790, 1415, 5831, 1437, 5871, 1373, 5860, 1368, 5916, 1331, 5940, 1335, 5973, 1379, 6023, 1528, 6019, 1596, 5993, 1588, 5977, 1599, 5984, 1608, 5971, 1625, 5972, 1650, 5944, 1676, 5939, 1730, 5907, 1786, 5906, 1787, 5718, 1819, 5717, 1806, 5428, 1839, 5429, 1838, 5456, 1808, 5487, 1732, 5497, 1651, 5488, 1595, 5468, 1551, 5486, 1540, 5467, 1549, 5433, 1481, 5442, 1416, 5426, 1394, 5454, 1335, 5449, 1268, 5471, 1256, 5470, 1224, 5505, 1212, 5535, 1158, 5527, 1175, 5535, 1187, 5526, 1205, 5532, 1243]], + "center":[5662, 1450], + "bbox":[4854.911490750158, 620.4912315367447, 1167.6361755384014, 1218.2194209192758] + }, + "DE":{ + "shape":[7082, 1965, 7100, 1940, 7132, 1937, 7133, 1938, 7135, 1938, 7119, 1976, 7130, 2008, 7129, 2009, 7158, 2039, 7187, 2099, 7225, 2115, 7236, 2145, 7222, 2135, 7217, 2144, 7232, 2147, 7205, 2160, 7219, 2166, 7236, 2147, 7246, 2177, 7246, 2178, 7242, 2179, 7242, 2179, 7239, 2180, 7234, 2181, 7150, 2199, 7082, 1965], + "center":[7157, 2107], + "bbox":[7082.430069572815, 1937.1742059381268, 163.18616844855842, 261.4809650452348] + }, + "ID":{ + "shape":[1428, 192, 1536, 217, 1550, 220, 1507, 416, 1537, 477, 1537, 498, 1526, 505, 1539, 522, 1521, 528, 1570, 572, 1620, 694, 1634, 689, 1638, 710, 1676, 715, 1641, 802, 1630, 805, 1638, 859, 1610, 874, 1616, 891, 1602, 915, 1628, 941, 1691, 907, 1704, 930, 1697, 943, 1706, 945, 1707, 996, 1731, 1041, 1724, 1076, 1764, 1103, 1769, 1166, 1787, 1189, 1802, 1167, 1854, 1183, 1871, 1163, 1982, 1188, 1979, 1168, 2000, 1150, 2037, 1210, 2037, 1210, 1959, 1681, 1903, 1671, 1536, 1604, 1535, 1604, 1118, 1512, 1117, 1512, 1197, 1168, 1231, 1110, 1195, 1080, 1194, 1057, 1234, 998, 1267, 976, 1276, 949, 1352, 851, 1346, 822, 1319, 800, 1309, 766, 1309, 764, 1304, 745, 1315, 731, 1306, 692, 1428, 192], + "center":[1499, 1209], + "bbox":[1116.7271597781803, 191.89745587373298, 920.1469178039938, 1488.964617046075] + }, + "IA":{ + "shape":[4779, 1552, 4779, 1554, 4782, 1580, 4804, 1599, 4789, 1625, 4809, 1695, 4860, 1714, 4871, 1739, 4871, 1740, 4911, 1792, 4944, 1810, 4945, 1869, 4908, 1927, 4825, 1952, 4817, 1986, 4843, 2012, 4843, 2044, 4825, 2066, 4822, 2096, 4782, 2118, 4789, 2150, 4781, 2154, 4781, 2154, 4734, 2111, 4171, 2132, 4143, 2131, 4125, 2102, 4136, 2071, 4126, 2040, 4131, 2018, 4119, 2014, 4127, 1995, 4115, 1986, 4121, 1965, 4095, 1948, 4099, 1898, 4089, 1865, 4074, 1860, 4059, 1827, 4054, 1768, 4045, 1765, 4044, 1765, 4018, 1718, 4047, 1642, 4026, 1613, 4035, 1591, 4025, 1570, 4045, 1570, 4097, 1570, 4775, 1553, 4779, 1552], + "center":[4463, 1860], + "bbox":[4018.187084563174, 1552.3336974056806, 926.7482669008059, 601.9610296119108] + }, + "MD":{ + "shape":[[7242, 2179, 7246, 2178, 7246, 2180, 7245, 2203, 7242, 2179], [6546, 2075, 6559, 2073, 7050, 1972, 7082, 1965, 7150, 2199, 7234, 2181, 7233, 2182, 7240, 2190, 7225, 2191, 7243, 2203, 7215, 2270, 7216, 2271, 7177, 2288, 7177, 2288, 7142, 2302, 7158, 2270, 7138, 2277, 7150, 2256, 7125, 2268, 7144, 2239, 7125, 2242, 7133, 2204, 7122, 2248, 7107, 2224, 7111, 2256, 7075, 2228, 7068, 2236, 7054, 2214, 7065, 2220, 7082, 2197, 7064, 2193, 7061, 2204, 7056, 2185, 7095, 2191, 7108, 2174, 7093, 2158, 7104, 2137, 7091, 2157, 7102, 2168, 7095, 2187, 7076, 2180, 7082, 2168, 7070, 2174, 7079, 2154, 7066, 2176, 7055, 2152, 7046, 2181, 7042, 2165, 7051, 2141, 7062, 2156, 7075, 2150, 7061, 2149, 7076, 2131, 7070, 2120, 7063, 2129, 7059, 2118, 7058, 2141, 7048, 2119, 7082, 2059, 7054, 2094, 7058, 2078, 7047, 2082, 7053, 2111, 7035, 2087, 7042, 2053, 7057, 2050, 7050, 2041, 7091, 2033, 7060, 2035, 7070, 2016, 7088, 2017, 7072, 2013, 7080, 1991, 7062, 2022, 7064, 1996, 7050, 2004, 7035, 2049, 7030, 2026, 7023, 2032, 7028, 2060, 7020, 2044, 7006, 2048, 7022, 2059, 7005, 2062, 7013, 2074, 6997, 2066, 7014, 2075, 7009, 2084, 6979, 2077, 6988, 2097, 6995, 2084, 6996, 2097, 7014, 2096, 7017, 2110, 7004, 2111, 7025, 2118, 6989, 2112, 7019, 2131, 6996, 2133, 7014, 2139, 7009, 2169, 7027, 2214, 7052, 2239, 7043, 2251, 7034, 2229, 7034, 2239, 7010, 2229, 6993, 2188, 6997, 2215, 7042, 2255, 7057, 2251, 7075, 2298, 7045, 2269, 7050, 2285, 7013, 2273, 7017, 2261, 7008, 2274, 7004, 2259, 7000, 2277, 6981, 2250, 6990, 2270, 6975, 2270, 6953, 2234, 6946, 2253, 6936, 2245, 6939, 2259, 6923, 2262, 6916, 2239, 6947, 2196, 6941, 2178, 6940, 2177, 6953, 2157, 6930, 2142, 6924, 2150, 6921, 2156, 6855, 2132, 6860, 2110, 6841, 2099, 6818, 2100, 6817, 2100, 6804, 2069, 6785, 2062, 6790, 2051, 6736, 2044, 6726, 2061, 6702, 2065, 6701, 2086, 6669, 2087, 6652, 2069, 6632, 2114, 6610, 2111, 6565, 2173, 6549, 2091, 6546, 2075]], + "center":[7052, 2222], + "bbox":[6546.235946891058, 1964.9808172764635, 699.4200881793276, 336.6030056060624] + }, + "MA":{ + "shape":[[7758, 1469, 7708, 1483, 7719, 1484, 7734, 1449, 7758, 1469], [7302, 1314, 7309, 1313, 7393, 1294, 7414, 1289, 7429, 1285, 7575, 1251, 7591, 1222, 7631, 1200, 7632, 1202, 7623, 1212, 7635, 1211, 7646, 1230, 7639, 1232, 7674, 1236, 7634, 1264, 7649, 1271, 7632, 1292, 7640, 1303, 7626, 1303, 7631, 1317, 7671, 1313, 7707, 1352, 7696, 1344, 7692, 1357, 7719, 1364, 7728, 1388, 7767, 1391, 7757, 1396, 7800, 1365, 7776, 1328, 7753, 1328, 7777, 1323, 7817, 1385, 7806, 1370, 7814, 1391, 7753, 1412, 7725, 1445, 7718, 1402, 7713, 1413, 7706, 1405, 7694, 1441, 7681, 1427, 7684, 1457, 7666, 1464, 7663, 1446, 7662, 1467, 7661, 1467, 7651, 1437, 7641, 1436, 7641, 1436, 7645, 1407, 7635, 1430, 7635, 1430, 7615, 1420, 7598, 1380, 7559, 1392, 7541, 1398, 7321, 1448, 7304, 1452, 7302, 1327, 7302, 1314], [7799, 1472, 7824, 1459, 7815, 1462, 7815, 1443, 7835, 1462, 7799, 1472]], + "center":[7490, 1333], + "bbox":[7301.785749503367, 1200.2581210721266, 533.1245306226901, 283.6107339386617] + }, + "AR":{ + "shape":[4323, 2915, 4351, 2914, 5015, 2888, 5031, 2925, 4986, 2986, 5087, 2978, 5089, 2978, 5089, 2979, 5102, 2996, 5083, 3000, 5093, 3013, 5055, 3032, 5073, 3047, 5057, 3059, 5065, 3073, 5046, 3067, 5048, 3098, 5038, 3082, 5026, 3095, 5041, 3100, 5028, 3122, 5045, 3148, 5009, 3177, 5009, 3178, 5020, 3191, 5011, 3206, 4986, 3199, 4990, 3227, 4974, 3219, 4972, 3234, 4988, 3238, 4978, 3250, 4969, 3241, 4974, 3291, 4961, 3311, 4944, 3302, 4932, 3333, 4919, 3328, 4939, 3340, 4915, 3350, 4930, 3357, 4928, 3369, 4897, 3380, 4914, 3418, 4890, 3428, 4909, 3437, 4877, 3438, 4893, 3452, 4878, 3462, 4878, 3485, 4888, 3473, 4897, 3481, 4886, 3495, 4907, 3483, 4894, 3504, 4900, 3521, 4911, 3516, 4905, 3542, 4887, 3545, 4900, 3558, 4894, 3568, 4879, 3569, 4464, 3582, 4426, 3583, 4424, 3481, 4369, 3483, 4354, 3465, 4352, 3464, 4352, 3449, 4356, 3126, 4327, 2945, 4323, 2915], + "center":[4662, 3220], + "bbox":[4322.829240491697, 2887.82523560477, 778.9827073906772, 694.879092657869] + }, + "IL":{ + "shape":[4871, 1739, 4872, 1739, 5275, 1711, 5276, 1714, 5277, 1759, 5328, 1857, 5328, 1858, 5370, 2310, 5358, 2319, 5366, 2336, 5355, 2353, 5379, 2385, 5386, 2426, 5342, 2519, 5321, 2525, 5333, 2543, 5316, 2567, 5323, 2594, 5311, 2594, 5322, 2613, 5322, 2614, 5305, 2642, 5320, 2674, 5261, 2698, 5266, 2759, 5185, 2735, 5158, 2769, 5164, 2782, 5166, 2783, 5147, 2769, 5138, 2770, 5143, 2784, 5127, 2775, 5103, 2732, 5116, 2710, 5098, 2652, 5045, 2615, 5030, 2621, 5032, 2605, 4964, 2560, 4960, 2538, 4992, 2437, 4940, 2417, 4915, 2433, 4895, 2364, 4796, 2279, 4770, 2203, 4780, 2155, 4781, 2154, 4789, 2150, 4782, 2118, 4822, 2096, 4825, 2066, 4843, 2044, 4843, 2012, 4817, 1986, 4825, 1952, 4908, 1927, 4945, 1869, 4944, 1810, 4911, 1792, 4871, 1740, 4871, 1739], + "center":[5115, 2145], + "bbox":[4770.196295410327, 1711.191338731059, 615.4388548089883, 1073.0606632626743] + }, + "UT":{ + "shape":[1536, 1604, 1903, 1671, 1959, 1681, 1956, 1693, 1927, 1871, 2214, 1915, 2213, 1925, 2105, 2676, 2039, 2667, 1351, 2546, 1345, 2545, 1536, 1604], + "center":[1781, 2167], + "bbox":[1344.750115055111, 1604.3978062032195, 869.4968003325662, 1072.015936358547] + }, + "IN":{ + "shape":[5428, 1839, 5717, 1806, 5718, 1819, 5721, 1845, 5778, 2313, 5777, 2313, 5768, 2324, 5779, 2338, 5773, 2350, 5788, 2355, 5786, 2374, 5734, 2399, 5695, 2396, 5701, 2433, 5675, 2455, 5666, 2484, 5645, 2489, 5637, 2538, 5620, 2553, 5585, 2539, 5567, 2513, 5574, 2523, 5553, 2530, 5550, 2566, 5531, 2585, 5501, 2558, 5472, 2578, 5462, 2603, 5385, 2574, 5383, 2602, 5340, 2587, 5340, 2610, 5323, 2613, 5322, 2613, 5311, 2594, 5323, 2594, 5316, 2567, 5333, 2543, 5321, 2525, 5342, 2519, 5386, 2426, 5379, 2385, 5355, 2353, 5366, 2336, 5358, 2319, 5370, 2310, 5328, 1858, 5329, 1858, 5367, 1872, 5428, 1839], + "center":[5553, 2130], + "bbox":[5310.914233466702, 1806.2693159343437, 476.59488846766635, 807.1441757493742] + }, + "MN":{ + "shape":[3957, 591, 3967, 564, 3950, 502, 3976, 503, 4218, 503, 4217, 428, 4242, 430, 4260, 439, 4284, 553, 4388, 573, 4395, 595, 4463, 568, 4503, 569, 4544, 585, 4534, 600, 4562, 603, 4581, 646, 4594, 641, 4593, 620, 4621, 617, 4635, 642, 4688, 664, 4687, 676, 4779, 632, 4793, 661, 4874, 652, 4906, 674, 4958, 667, 4798, 754, 4628, 929, 4629, 930, 4604, 948, 4609, 1063, 4558, 1095, 4534, 1135, 4532, 1162, 4550, 1165, 4566, 1188, 4551, 1218, 4548, 1321, 4585, 1355, 4614, 1357, 4667, 1391, 4690, 1430, 4744, 1458, 4772, 1499, 4779, 1552, 4775, 1553, 4097, 1570, 4045, 1570, 4047, 1222, 3992, 1163, 4029, 1123, 4033, 1100, 4033, 1099, 4028, 1023, 4001, 959, 4008, 907, 3997, 891, 3996, 776, 3959, 670, 3957, 591], + "center":[4328, 1050], + "bbox":[3950.4253913269413, 427.57079578020245, 1007.2005431623966, 1142.3275358037831] + }, + "AZ":{ + "shape":[2105, 2676, 1948, 3757, 1919, 3753, 1616, 3706, 1051, 3370, 1074, 3333, 1075, 3331, 1106, 3331, 1121, 3315, 1121, 3280, 1092, 3262, 1105, 3229, 1097, 3220, 1102, 3200, 1139, 3179, 1153, 3106, 1178, 3078, 1229, 3058, 1196, 3013, 1193, 2962, 1174, 2927, 1179, 2903, 1179, 2903, 1179, 2902, 1195, 2871, 1202, 2695, 1260, 2695, 1276, 2722, 1293, 2725, 1315, 2697, 1345, 2545, 1345, 2545, 1351, 2546, 2039, 2667, 2105, 2676], + "center":[1610, 3091], + "bbox":[1051.3556136955926, 2544.815557518358, 1053.5072639848177, 1212.212023750329] + }, + "MO":{ + "shape":[4781, 2154, 4780, 2155, 4770, 2203, 4796, 2279, 4895, 2364, 4915, 2433, 4940, 2417, 4992, 2437, 4960, 2538, 4964, 2560, 5032, 2605, 5030, 2621, 5045, 2615, 5098, 2652, 5116, 2710, 5103, 2732, 5127, 2775, 5143, 2784, 5138, 2770, 5147, 2769, 5166, 2783, 5167, 2783, 5161, 2817, 5171, 2825, 5159, 2831, 5160, 2860, 5137, 2853, 5129, 2880, 5128, 2881, 5118, 2882, 5118, 2880, 5110, 2865, 5109, 2880, 5110, 2881, 5115, 2907, 5100, 2914, 5113, 2927, 5089, 2931, 5107, 2950, 5089, 2978, 5087, 2978, 4986, 2986, 5031, 2925, 5015, 2888, 4351, 2914, 4323, 2915, 4322, 2863, 4321, 2819, 4319, 2406, 4282, 2393, 4274, 2360, 4241, 2331, 4262, 2293, 4277, 2292, 4266, 2264, 4242, 2269, 4210, 2245, 4211, 2244, 4195, 2236, 4185, 2197, 4159, 2184, 4159, 2139, 4142, 2133, 4143, 2131, 4171, 2132, 4734, 2111, 4781, 2154, 4781, 2154], + "center":[4649, 2529], + "bbox":[4141.599891262755, 2111.1869114797505, 1029.5962543407404, 874.9905328579766] + }, + "MT":{ + "shape":[3074, 458, 3023, 1050, 3022, 1057, 3005, 1232, 3001, 1232, 2053, 1110, 2042, 1174, 2037, 1210, 2000, 1150, 1979, 1168, 1982, 1188, 1871, 1163, 1854, 1183, 1802, 1167, 1787, 1189, 1769, 1166, 1764, 1103, 1724, 1076, 1731, 1041, 1707, 996, 1706, 945, 1697, 943, 1704, 930, 1691, 907, 1628, 941, 1602, 915, 1616, 891, 1610, 874, 1638, 859, 1630, 805, 1641, 802, 1676, 715, 1638, 710, 1634, 689, 1620, 694, 1570, 572, 1521, 528, 1539, 522, 1526, 505, 1537, 498, 1537, 477, 1507, 416, 1550, 220, 1560, 222, 3074, 458], + "center":[2366, 706], + "bbox":[1506.9365729336703, 219.959812182772, 1567.488761621926, 1012.1411673843539] + }, + "MS":{ + "shape":[5342, 3152, 5360, 3170, 5349, 3751, 5385, 4042, 5385, 4042, 5370, 4056, 5286, 4042, 5308, 4047, 5237, 4070, 5243, 4062, 5228, 4057, 5213, 4094, 5200, 4096, 5199, 4096, 5183, 4090, 5138, 4007, 5153, 3940, 4836, 3959, 4848, 3948, 4831, 3910, 4853, 3903, 4842, 3879, 4858, 3885, 4850, 3857, 4869, 3845, 4850, 3837, 4867, 3838, 4872, 3813, 4891, 3811, 4873, 3810, 4877, 3794, 4892, 3798, 4918, 3759, 4905, 3747, 4918, 3752, 4929, 3733, 4903, 3736, 4902, 3724, 4931, 3722, 4948, 3689, 4928, 3692, 4932, 3675, 4908, 3669, 4910, 3656, 4928, 3664, 4913, 3654, 4922, 3637, 4900, 3641, 4915, 3623, 4897, 3615, 4912, 3587, 4903, 3571, 4889, 3586, 4894, 3569, 4894, 3568, 4900, 3558, 4887, 3545, 4905, 3542, 4911, 3516, 4900, 3521, 4894, 3504, 4907, 3483, 4886, 3495, 4897, 3481, 4888, 3473, 4878, 3485, 4878, 3462, 4893, 3452, 4877, 3438, 4909, 3437, 4890, 3428, 4914, 3418, 4897, 3380, 4928, 3369, 4930, 3357, 4915, 3350, 4939, 3340, 4919, 3328, 4932, 3333, 4944, 3302, 4961, 3311, 4974, 3291, 4969, 3241, 4978, 3250, 4988, 3238, 4972, 3234, 4974, 3219, 4990, 3227, 4986, 3199, 5011, 3206, 5020, 3191, 5009, 3178, 5009, 3177, 5014, 3177, 5341, 3152, 5342, 3152], + "center":[5130, 3618], + "bbox":[4830.8439535221705, 3152.185506272538, 554.1423743161158, 943.8294240195419] + }, + "NH":{ + "shape":[7427, 828, 7425, 784, 7467, 758, 7468, 758, 7581, 1111, 7613, 1135, 7616, 1152, 7616, 1153, 7608, 1172, 7618, 1155, 7636, 1165, 7631, 1199, 7631, 1200, 7591, 1222, 7575, 1251, 7429, 1285, 7414, 1289, 7394, 1266, 7402, 1236, 7383, 1128, 7404, 1022, 7390, 984, 7444, 926, 7445, 909, 7424, 882, 7434, 848, 7427, 828], + "center":[7487, 1110], + "bbox":[7383.275752846597, 757.6279479694789, 253.13402332287387, 530.9874508377478] + }, + "NJ":{ + "shape":[7167, 1623, 7175, 1625, 7297, 1663, 7292, 1722, 7290, 1722, 7255, 1776, 7310, 1776, 7302, 1788, 7313, 1783, 7311, 1794, 7305, 1766, 7312, 1771, 7319, 1834, 7309, 1835, 7321, 1839, 7325, 1898, 7317, 1844, 7308, 1847, 7317, 1847, 7308, 1852, 7315, 1867, 7304, 1869, 7317, 1870, 7319, 1912, 7299, 1943, 7306, 1955, 7288, 1950, 7297, 1971, 7285, 1979, 7298, 1986, 7264, 2005, 7281, 2005, 7260, 2061, 7254, 2048, 7258, 2069, 7235, 2086, 7234, 2040, 7200, 2046, 7131, 2007, 7130, 2008, 7119, 1976, 7135, 1938, 7133, 1938, 7217, 1848, 7157, 1810, 7151, 1788, 7132, 1786, 7125, 1764, 7138, 1727, 7122, 1706, 7166, 1623, 7167, 1623], + "center":[7248, 1862], + "bbox":[7119.486993899072, 1622.7122776765768, 205.74226205555533, 463.7265235430323] + }, + "NM":{ + "shape":[2105, 2676, 2182, 2687, 3017, 2778, 3031, 2779, 3030, 2791, 3023, 2874, 3017, 2874, 2946, 3738, 2363, 3681, 2358, 3706, 2373, 3724, 2337, 3720, 2098, 3690, 2086, 3776, 1953, 3758, 1948, 3757, 2105, 2676], + "center":[2533, 3173], + "bbox":[1947.8141535882821, 2676.4137425617664, 1082.71177385349, 1099.5607135602122] + }, + "AK":{ + "shape":[620, 3939, 642, 3931, 650, 3935, 643, 3931, 675, 3922, 680, 3925, 667, 3924, 694, 3939, 689, 3941, 699, 3939, 714, 3945, 716, 3951, 700, 3957, 686, 3954, 692, 3958, 682, 3955, 685, 3953, 664, 3953, 690, 3958, 684, 3960, 693, 3962, 682, 3961, 693, 3964, 689, 3969, 696, 3965, 713, 3966, 721, 3973, 725, 3971, 714, 3968, 714, 3961, 731, 3962, 728, 3956, 734, 3954, 738, 3960, 738, 3953, 741, 3955, 737, 3962, 744, 3956, 760, 3968, 750, 3974, 776, 3987, 791, 3982, 827, 3987, 846, 3994, 850, 4005, 853, 4004, 848, 3997, 850, 4001, 853, 3998, 850, 3995, 871, 4003, 873, 4005, 853, 4012, 877, 4022, 850, 4017, 895, 4026, 880, 4032, 906, 4037, 917, 4045, 921, 4039, 923, 4053, 922, 4040, 943, 4043, 939, 4039, 953, 4044, 938, 4048, 938, 4051, 983, 4048, 1003, 4051, 1044, 4068, 1047, 4075, 1079, 4080, 1086, 4089, 1174, 4108, 1186, 4119, 1216, 4130, 1302, 4135, 1377, 4173, 1388, 4183, 1397, 4183, 1393, 4181, 1406, 4186, 1300, 4789, 1324, 4799, 1329, 4794, 1351, 4806, 1368, 4799, 1399, 4803, 1390, 4819, 1413, 4835, 1416, 4845, 1463, 4888, 1463, 4910, 1502, 4900, 1515, 4901, 1522, 4895, 1523, 4883, 1536, 4881, 1531, 4875, 1578, 4869, 1589, 4878, 1599, 4888, 1594, 4896, 1599, 4907, 1612, 4911, 1622, 4921, 1631, 4940, 1651, 4952, 1672, 4975, 1668, 4977, 1719, 5064, 1712, 5071, 1728, 5076, 1722, 5087, 1735, 5093, 1735, 5107, 1748, 5108, 1803, 5141, 1804, 5147, 1821, 5151, 1823, 5165, 1813, 5177, 1819, 5207, 1789, 5233, 1782, 5235, 1791, 5229, 1781, 5233, 1780, 5224, 1781, 5235, 1776, 5236, 1776, 5222, 1775, 5232, 1765, 5232, 1764, 5223, 1769, 5219, 1765, 5221, 1765, 5214, 1773, 5212, 1780, 5220, 1779, 5213, 1790, 5212, 1780, 5212, 1794, 5200, 1777, 5214, 1775, 5207, 1769, 5212, 1761, 5210, 1760, 5205, 1769, 5200, 1786, 5202, 1783, 5199, 1788, 5195, 1781, 5200, 1773, 5198, 1775, 5184, 1784, 5181, 1785, 5185, 1788, 5178, 1775, 5183, 1775, 5173, 1786, 5171, 1775, 5172, 1775, 5165, 1771, 5166, 1763, 5155, 1769, 5146, 1755, 5152, 1740, 5149, 1733, 5154, 1728, 5150, 1732, 5155, 1723, 5154, 1731, 5159, 1727, 5165, 1723, 5162, 1727, 5168, 1722, 5172, 1715, 5165, 1720, 5172, 1717, 5178, 1708, 5172, 1703, 5158, 1718, 5159, 1714, 5154, 1716, 5148, 1724, 5147, 1723, 5134, 1743, 5138, 1749, 5133, 1727, 5130, 1726, 5120, 1715, 5119, 1707, 5107, 1709, 5100, 1697, 5099, 1705, 5098, 1698, 5094, 1709, 5088, 1700, 5092, 1688, 5084, 1680, 5073, 1689, 5076, 1688, 5067, 1678, 5070, 1669, 5064, 1671, 5059, 1666, 5058, 1663, 5063, 1650, 5055, 1655, 5049, 1670, 5055, 1667, 5051, 1678, 5048, 1658, 5045, 1664, 5040, 1657, 5040, 1656, 5032, 1667, 5030, 1653, 5030, 1649, 5025, 1651, 5020, 1686, 5039, 1655, 5017, 1656, 5007, 1679, 5014, 1657, 5006, 1649, 5014, 1643, 5003, 1648, 5001, 1652, 5005, 1653, 5000, 1648, 4997, 1655, 4991, 1642, 5001, 1634, 4994, 1635, 4980, 1641, 4978, 1639, 4974, 1632, 4978, 1630, 4984, 1600, 4967, 1600, 4958, 1591, 4947, 1596, 4948, 1593, 4938, 1597, 4929, 1590, 4942, 1578, 4904, 1581, 4891, 1577, 4903, 1568, 4899, 1580, 4917, 1569, 4906, 1575, 4916, 1574, 4927, 1583, 4949, 1578, 4948, 1585, 4968, 1582, 4974, 1571, 4972, 1563, 4953, 1563, 4962, 1556, 4957, 1540, 4958, 1544, 4953, 1539, 4953, 1545, 4953, 1543, 4946, 1547, 4944, 1536, 4928, 1553, 4926, 1539, 4926, 1534, 4913, 1535, 4919, 1529, 4920, 1536, 4921, 1536, 4925, 1528, 4931, 1520, 4925, 1527, 4926, 1516, 4924, 1517, 4915, 1511, 4918, 1506, 4909, 1507, 4919, 1488, 4904, 1495, 4913, 1482, 4919, 1490, 4915, 1512, 4924, 1516, 4927, 1508, 4923, 1511, 4927, 1508, 4927, 1522, 4935, 1510, 4938, 1520, 4941, 1525, 4938, 1531, 4945, 1526, 4947, 1533, 4948, 1535, 4957, 1521, 4959, 1520, 4953, 1518, 4955, 1510, 4948, 1511, 4953, 1506, 4952, 1510, 4957, 1515, 4954, 1517, 4960, 1505, 4954, 1502, 4955, 1505, 4962, 1500, 4963, 1500, 4958, 1491, 4955, 1491, 4950, 1479, 4948, 1454, 4929, 1463, 4926, 1453, 4928, 1441, 4915, 1440, 4906, 1423, 4893, 1426, 4893, 1410, 4887, 1415, 4888, 1418, 4884, 1407, 4884, 1409, 4887, 1381, 4869, 1385, 4871, 1381, 4865, 1376, 4866, 1350, 4850, 1362, 4849, 1371, 4842, 1366, 4829, 1375, 4823, 1382, 4833, 1378, 4841, 1381, 4849, 1377, 4849, 1380, 4853, 1383, 4850, 1382, 4835, 1403, 4840, 1385, 4834, 1374, 4819, 1355, 4831, 1329, 4835, 1272, 4813, 1283, 4808, 1278, 4797, 1274, 4798, 1278, 4804, 1261, 4806, 1212, 4788, 1149, 4783, 1131, 4777, 1145, 4779, 1133, 4770, 1136, 4765, 1123, 4767, 1111, 4761, 1114, 4762, 1107, 4754, 1099, 4754, 1110, 4745, 1104, 4742, 1109, 4733, 1080, 4747, 1072, 4741, 1081, 4741, 1050, 4732, 1069, 4722, 1037, 4719, 1050, 4714, 1049, 4710, 1043, 4714, 1035, 4713, 1037, 4709, 1025, 4714, 1017, 4711, 1015, 4707, 1049, 4708, 1041, 4702, 1034, 4706, 1020, 4703, 1015, 4693, 1025, 4696, 1017, 4692, 1021, 4689, 1030, 4693, 1024, 4686, 1043, 4689, 1040, 4686, 1025, 4682, 1004, 4692, 1000, 4691, 1005, 4688, 999, 4686, 994, 4690, 992, 4684, 982, 4692, 979, 4688, 984, 4685, 979, 4686, 983, 4682, 974, 4688, 981, 4678, 979, 4672, 974, 4681, 969, 4680, 973, 4681, 968, 4684, 973, 4685, 971, 4691, 965, 4684, 960, 4687, 964, 4691, 959, 4691, 949, 4681, 971, 4668, 967, 4667, 971, 4663, 949, 4677, 947, 4668, 933, 4670, 935, 4672, 929, 4674, 945, 4672, 940, 4679, 935, 4677, 938, 4680, 935, 4685, 931, 4683, 933, 4686, 914, 4684, 927, 4686, 914, 4693, 930, 4688, 926, 4698, 938, 4690, 933, 4695, 938, 4700, 930, 4705, 927, 4700, 920, 4700, 910, 4706, 924, 4702, 932, 4708, 931, 4711, 942, 4702, 945, 4706, 942, 4707, 948, 4710, 941, 4712, 949, 4714, 923, 4720, 926, 4724, 920, 4726, 932, 4723, 928, 4726, 933, 4727, 931, 4731, 938, 4727, 927, 4732, 925, 4727, 917, 4741, 913, 4735, 907, 4741, 882, 4733, 887, 4728, 879, 4729, 873, 4737, 874, 4728, 869, 4720, 869, 4729, 855, 4736, 858, 4745, 853, 4742, 856, 4742, 852, 4739, 856, 4733, 851, 4727, 849, 4735, 843, 4732, 847, 4737, 847, 4747, 834, 4734, 836, 4744, 831, 4746, 827, 4740, 828, 4747, 810, 4752, 822, 4736, 806, 4751, 807, 4747, 802, 4746, 807, 4742, 798, 4744, 801, 4751, 780, 4756, 783, 4760, 778, 4763, 776, 4754, 761, 4752, 770, 4761, 757, 4753, 744, 4757, 746, 4759, 727, 4749, 731, 4742, 739, 4746, 734, 4739, 742, 4738, 744, 4743, 747, 4737, 764, 4746, 756, 4738, 762, 4742, 758, 4736, 771, 4737, 772, 4731, 788, 4725, 757, 4729, 760, 4732, 739, 4717, 751, 4702, 769, 4692, 780, 4673, 784, 4674, 778, 4670, 776, 4660, 832, 4650, 840, 4659, 863, 4661, 900, 4679, 892, 4669, 870, 4662, 849, 4646, 871, 4635, 896, 4634, 886, 4631, 889, 4630, 869, 4631, 855, 4642, 830, 4636, 826, 4627, 825, 4632, 807, 4632, 793, 4641, 761, 4643, 757, 4647, 760, 4657, 742, 4654, 739, 4661, 725, 4666, 729, 4672, 711, 4679, 684, 4670, 693, 4671, 707, 4691, 693, 4699, 669, 4697, 683, 4700, 679, 4708, 670, 4710, 659, 4707, 664, 4703, 659, 4698, 656, 4707, 651, 4707, 650, 4702, 644, 4705, 651, 4708, 649, 4711, 638, 4711, 640, 4718, 618, 4717, 628, 4720, 609, 4731, 613, 4733, 612, 4740, 637, 4741, 656, 4760, 636, 4771, 620, 4769, 617, 4776, 608, 4775, 611, 4783, 592, 4785, 604, 4788, 597, 4788, 602, 4792, 593, 4791, 597, 4796, 591, 4793, 591, 4798, 584, 4788, 584, 4796, 577, 4792, 577, 4799, 554, 4794, 548, 4798, 549, 4803, 535, 4803, 536, 4810, 521, 4802, 520, 4811, 510, 4810, 511, 4817, 496, 4812, 492, 4814, 494, 4820, 486, 4816, 466, 4822, 477, 4827, 470, 4829, 474, 4834, 467, 4838, 458, 4836, 461, 4844, 450, 4837, 447, 4846, 440, 4839, 441, 4843, 431, 4848, 427, 4844, 427, 4850, 412, 4842, 414, 4848, 403, 4848, 409, 4856, 398, 4855, 393, 4849, 373, 4854, 388, 4855, 386, 4861, 373, 4855, 372, 4859, 358, 4856, 340, 4865, 348, 4868, 355, 4863, 353, 4866, 366, 4869, 355, 4870, 357, 4875, 370, 4873, 356, 4878, 355, 4873, 350, 4876, 352, 4881, 348, 4877, 350, 4885, 344, 4882, 348, 4880, 342, 4880, 351, 4872, 343, 4870, 346, 4873, 337, 4871, 337, 4885, 329, 4879, 324, 4884, 300, 4882, 296, 4887, 295, 4879, 292, 4879, 292, 4887, 283, 4899, 276, 4895, 284, 4894, 279, 4891, 288, 4884, 276, 4877, 264, 4879, 262, 4885, 257, 4881, 257, 4886, 242, 4882, 245, 4887, 239, 4887, 235, 4893, 233, 4884, 222, 4886, 227, 4892, 216, 4885, 196, 4892, 182, 4888, 185, 4880, 193, 4875, 203, 4881, 182, 4871, 160, 4892, 152, 4890, 154, 4899, 146, 4897, 149, 4893, 141, 4895, 140, 4901, 137, 4896, 130, 4898, 126, 4891, 134, 4894, 122, 4878, 117, 4882, 123, 4889, 118, 4893, 122, 4901, 118, 4893, 113, 4899, 104, 4898, 92, 4881, 88, 4888, 97, 4895, 78, 4900, 84, 4894, 81, 4891, 87, 4893, 83, 4882, 103, 4876, 98, 4880, 109, 4882, 109, 4877, 124, 4872, 133, 4875, 133, 4870, 125, 4870, 174, 4850, 224, 4855, 224, 4859, 215, 4860, 224, 4874, 232, 4874, 227, 4863, 253, 4877, 250, 4873, 254, 4872, 238, 4864, 257, 4842, 287, 4833, 320, 4831, 317, 4829, 336, 4824, 352, 4833, 353, 4815, 373, 4803, 391, 4802, 387, 4799, 405, 4793, 412, 4792, 410, 4800, 415, 4803, 408, 4807, 415, 4803, 413, 4798, 425, 4804, 417, 4802, 429, 4797, 415, 4799, 415, 4789, 419, 4787, 411, 4788, 423, 4761, 429, 4754, 448, 4761, 440, 4753, 433, 4754, 431, 4741, 458, 4726, 483, 4736, 460, 4724, 468, 4712, 476, 4710, 477, 4703, 489, 4701, 482, 4700, 461, 4715, 402, 4720, 385, 4703, 395, 4694, 392, 4691, 409, 4694, 416, 4703, 393, 4679, 396, 4688, 378, 4696, 375, 4689, 379, 4688, 374, 4689, 378, 4693, 371, 4704, 368, 4701, 372, 4699, 363, 4699, 357, 4692, 363, 4699, 370, 4700, 367, 4703, 374, 4721, 363, 4725, 354, 4721, 341, 4694, 330, 4687, 334, 4681, 326, 4680, 325, 4686, 316, 4689, 313, 4680, 305, 4679, 307, 4675, 301, 4677, 298, 4664, 270, 4675, 268, 4669, 246, 4669, 251, 4672, 240, 4677, 244, 4677, 223, 4680, 219, 4680, 221, 4674, 220, 4678, 199, 4669, 214, 4672, 229, 4666, 222, 4666, 222, 4648, 238, 4647, 225, 4643, 222, 4647, 216, 4639, 216, 4630, 219, 4628, 218, 4633, 222, 4625, 236, 4620, 219, 4577, 220, 4568, 226, 4567, 220, 4565, 215, 4572, 211, 4560, 235, 4542, 227, 4542, 217, 4552, 213, 4549, 215, 4555, 204, 4555, 206, 4561, 198, 4559, 210, 4566, 203, 4578, 192, 4574, 190, 4578, 158, 4579, 121, 4568, 117, 4560, 124, 4558, 111, 4549, 100, 4531, 108, 4528, 108, 4523, 116, 4522, 112, 4517, 134, 4515, 139, 4510, 152, 4514, 150, 4525, 167, 4523, 171, 4518, 146, 4505, 167, 4507, 155, 4502, 159, 4499, 145, 4506, 85, 4484, 88, 4480, 98, 4483, 99, 4477, 108, 4479, 90, 4476, 88, 4472, 95, 4468, 93, 4464, 94, 4468, 89, 4471, 80, 4465, 89, 4463, 87, 4458, 94, 4453, 120, 4448, 114, 4443, 111, 4441, 118, 4448, 91, 4453, 92, 4457, 86, 4458, 88, 4463, 79, 4464, 77, 4473, 67, 4468, 69, 4456, 56, 4450, 55, 4444, 65, 4446, 64, 4439, 57, 4435, 47, 4436, 45, 4429, 49, 4421, 48, 4427, 72, 4431, 56, 4423, 53, 4416, 81, 4421, 74, 4414, 76, 4404, 114, 4384, 128, 4386, 126, 4390, 139, 4396, 131, 4399, 140, 4397, 135, 4394, 137, 4390, 127, 4390, 131, 4384, 131, 4371, 134, 4372, 130, 4370, 148, 4375, 132, 4367, 160, 4349, 194, 4357, 195, 4364, 202, 4365, 211, 4376, 247, 4370, 271, 4358, 270, 4354, 284, 4361, 275, 4364, 328, 4370, 350, 4358, 351, 4353, 348, 4327, 335, 4312, 339, 4312, 322, 4308, 331, 4302, 348, 4308, 346, 4305, 362, 4304, 362, 4289, 349, 4280, 358, 4282, 362, 4277, 327, 4287, 317, 4278, 321, 4282, 298, 4282, 301, 4283, 274, 4288, 259, 4298, 259, 4287, 249, 4281, 247, 4273, 234, 4274, 250, 4282, 242, 4289, 230, 4277, 210, 4269, 157, 4262, 156, 4259, 155, 4264, 164, 4265, 151, 4263, 86, 4232, 84, 4222, 90, 4218, 66, 4192, 74, 4186, 69, 4196, 86, 4200, 81, 4199, 98, 4193, 113, 4200, 129, 4217, 146, 4211, 121, 4205, 120, 4208, 104, 4192, 47, 4171, 17, 4146, 34, 4143, 20, 4148, 47, 4149, 50, 4148, 46, 4144, 53, 4145, 49, 4143, 64, 4141, 74, 4147, 83, 4145, 79, 4141, 118, 4137, 127, 4144, 156, 4150, 137, 4138, 153, 4133, 177, 4135, 182, 4140, 199, 4133, 216, 4135, 254, 4146, 239, 4141, 247, 4147, 238, 4154, 238, 4162, 219, 4162, 234, 4165, 240, 4176, 289, 4186, 291, 4193, 318, 4194, 333, 4206, 340, 4199, 336, 4200, 352, 4190, 374, 4198, 367, 4207, 377, 4200, 373, 4191, 354, 4182, 334, 4180, 337, 4186, 333, 4185, 337, 4169, 324, 4153, 311, 4149, 307, 4139, 316, 4135, 336, 4151, 331, 4159, 351, 4178, 373, 4177, 389, 4192, 417, 4198, 420, 4190, 416, 4183, 421, 4182, 418, 4180, 406, 4182, 377, 4171, 369, 4176, 357, 4174, 341, 4157, 353, 4145, 364, 4147, 357, 4140, 293, 4124, 299, 4128, 257, 4109, 255, 4089, 244, 4072, 152, 4004, 125, 3993, 147, 3993, 130, 3993, 157, 3998, 148, 3993, 158, 3986, 164, 3966, 256, 3987, 292, 3985, 321, 3970, 313, 3977, 321, 3975, 329, 3963, 326, 3959, 337, 3956, 335, 3951, 394, 3929, 406, 3935, 396, 3933, 402, 3937, 383, 3937, 439, 3938, 443, 3942, 441, 3939, 487, 3931, 497, 3935, 496, 3938, 481, 3939, 495, 3941, 482, 3949, 491, 3948, 498, 3962, 495, 3948, 501, 3942, 527, 3947, 504, 3943, 498, 3933, 487, 3930, 537, 3925, 528, 3925, 539, 3929, 521, 3927, 533, 3937, 531, 3931, 546, 3933, 541, 3930, 588, 3940, 620, 3939], + "center":[813, 4358], + "bbox":[17.226250526593294, 3922.1622828273585, 1806.1690621660666, 1313.3765572801585] + }, + "TX":{ + "shape":[[3960, 4548, 3926, 4590, 3956, 4534, 4038, 4483, 3960, 4548], [3765, 3367, 3774, 3393, 3796, 3396, 3793, 3416, 3813, 3422, 3840, 3397, 3880, 3431, 3914, 3413, 3927, 3451, 3950, 3405, 3986, 3430, 4014, 3417, 4009, 3427, 4053, 3458, 4085, 3428, 4145, 3427, 4173, 3408, 4222, 3421, 4233, 3404, 4291, 3445, 4352, 3464, 4354, 3465, 4369, 3483, 4424, 3481, 4426, 3583, 4426, 3596, 4431, 3781, 4468, 3822, 4467, 3859, 4487, 3873, 4481, 3881, 4500, 3900, 4492, 3912, 4519, 3935, 4524, 3965, 4488, 4061, 4497, 4154, 4459, 4204, 4475, 4227, 4474, 4229, 4432, 4233, 4316, 4293, 4369, 4255, 4315, 4261, 4329, 4237, 4323, 4210, 4296, 4236, 4284, 4227, 4275, 4256, 4294, 4268, 4282, 4276, 4310, 4294, 4298, 4288, 4297, 4303, 4263, 4330, 4241, 4321, 4255, 4349, 4233, 4348, 4247, 4349, 4238, 4364, 4258, 4351, 4229, 4378, 4162, 4418, 4119, 4424, 4115, 4438, 4160, 4418, 4045, 4481, 4112, 4432, 4068, 4448, 4060, 4430, 4018, 4451, 4040, 4474, 3994, 4498, 3973, 4481, 3973, 4514, 3933, 4533, 3902, 4593, 3888, 4582, 3868, 4592, 3870, 4608, 3892, 4619, 3845, 4737, 3850, 4754, 3834, 4754, 3850, 4780, 3833, 4783, 3859, 4840, 3847, 4861, 3873, 4881, 3876, 4927, 3897, 4936, 3873, 4955, 3896, 4954, 3903, 4938, 3905, 4957, 3879, 4958, 3854, 4981, 3816, 4943, 3718, 4936, 3676, 4902, 3636, 4898, 3613, 4872, 3559, 4859, 3533, 4775, 3504, 4740, 3506, 4694, 3490, 4681, 3496, 4633, 3435, 4586, 3426, 4549, 3370, 4492, 3362, 4445, 3335, 4412, 3309, 4324, 3247, 4252, 3215, 4237, 3218, 4218, 3207, 4226, 3194, 4188, 3081, 4178, 3040, 4157, 3027, 4179, 2978, 4179, 2937, 4252, 2938, 4277, 2915, 4284, 2892, 4321, 2863, 4319, 2783, 4256, 2738, 4240, 2665, 4174, 2641, 4116, 2644, 4054, 2622, 4023, 2615, 3980, 2540, 3924, 2459, 3816, 2420, 3792, 2396, 3737, 2373, 3725, 2373, 3724, 2358, 3706, 2363, 3681, 2946, 3738, 3017, 2874, 3023, 2874, 3487, 2903, 3471, 3276, 3484, 3274, 3520, 3312, 3565, 3315, 3571, 3299, 3595, 3323, 3598, 3347, 3692, 3360, 3710, 3380, 3729, 3362, 3765, 3367], [3892, 4640, 3925, 4591, 3867, 4713, 3892, 4640], [3880, 4832, 3866, 4788, 3868, 4714, 3868, 4773, 3902, 4937, 3880, 4832]], + "center":[3629, 3892], + "bbox":[2357.5862085056892, 2873.8949636291673, 2166.2516199112247, 2106.8870904247105] + }, + "AL":{ + "shape":[5342, 3152, 5751, 3113, 5757, 3137, 5866, 3512, 5911, 3591, 5907, 3611, 5927, 3621, 5902, 3649, 5896, 3708, 5919, 3762, 5916, 3831, 5939, 3865, 5910, 3869, 5508, 3912, 5506, 3942, 5548, 3975, 5545, 4009, 5544, 4009, 5552, 4017, 5539, 4037, 5516, 4037, 5518, 4048, 5534, 4045, 5527, 4052, 5450, 4070, 5500, 4054, 5480, 4029, 5466, 4030, 5458, 3981, 5441, 3978, 5426, 4000, 5435, 4008, 5430, 4053, 5386, 4042, 5385, 4042, 5349, 3751, 5360, 3170, 5342, 3152], + "center":[5617, 3547], + "bbox":[5342.095656308308, 3113.4827673729174, 596.451313025831, 956.3113443753509] + }, + "NC":{ + "shape":[[7201, 2563, 7205, 2562, 7205, 2563, 7287, 2693, 7245, 2651, 7201, 2563], [6319, 2725, 6333, 2723, 7179, 2568, 7180, 2569, 7175, 2579, 7200, 2600, 7202, 2588, 7236, 2649, 7199, 2610, 7200, 2632, 7214, 2636, 7164, 2617, 7193, 2645, 7174, 2653, 7155, 2639, 7171, 2659, 7130, 2648, 7157, 2663, 7130, 2669, 7142, 2671, 7119, 2688, 7091, 2665, 7093, 2636, 7052, 2626, 7089, 2638, 7098, 2707, 7206, 2677, 7191, 2684, 7209, 2684, 7198, 2697, 7209, 2714, 7198, 2716, 7215, 2729, 7199, 2731, 7207, 2736, 7221, 2731, 7215, 2690, 7234, 2690, 7224, 2680, 7239, 2671, 7264, 2716, 7255, 2721, 7260, 2741, 7239, 2733, 7244, 2746, 7212, 2802, 7178, 2792, 7173, 2806, 7168, 2785, 7162, 2804, 7151, 2800, 7144, 2792, 7154, 2788, 7138, 2782, 7161, 2779, 7151, 2766, 7116, 2785, 7138, 2787, 7145, 2804, 7057, 2791, 7084, 2815, 7089, 2804, 7128, 2813, 7118, 2828, 7130, 2815, 7140, 2816, 7137, 2828, 7148, 2815, 7167, 2824, 7127, 2856, 7161, 2848, 7127, 2892, 7076, 2863, 7086, 2873, 7072, 2883, 7086, 2874, 7112, 2907, 7127, 2896, 7138, 2910, 7143, 2889, 7156, 2898, 7153, 2881, 7174, 2896, 7160, 2880, 7175, 2883, 7175, 2859, 7185, 2890, 7203, 2865, 7203, 2890, 7192, 2892, 7181, 2928, 7161, 2914, 7166, 2933, 7152, 2917, 7140, 2930, 7153, 2934, 7093, 2956, 7082, 2940, 7089, 2954, 7077, 2954, 7084, 2960, 7063, 2986, 7048, 2983, 7060, 2970, 7038, 2953, 7051, 2971, 7040, 2980, 7056, 2994, 7003, 3047, 6991, 3097, 6976, 3076, 6979, 3133, 6944, 3128, 6892, 3152, 6891, 3152, 6686, 3010, 6511, 3038, 6509, 3016, 6480, 2987, 6465, 3002, 6461, 2982, 6249, 3003, 6143, 3060, 6140, 3060, 5983, 3084, 5953, 3088, 5952, 3042, 5989, 3028, 5990, 3001, 6009, 2978, 6067, 2961, 6114, 2916, 6140, 2910, 6147, 2879, 6165, 2877, 6186, 2847, 6202, 2866, 6229, 2830, 6278, 2822, 6293, 2785, 6322, 2773, 6319, 2725, 6319, 2725], [7183, 2567, 7187, 2566, 7187, 2566, 7203, 2576, 7183, 2567], [7288, 2695, 7308, 2729, 7313, 2798, 7281, 2813, 7311, 2788, 7306, 2729, 7288, 2695]], + "center":[6700, 2826], + "bbox":[5952.042953294017, 2562.1635913643336, 1361.0084056628302, 590.1898019127811] + }, + "ND":{ + "shape":[3950, 502, 3967, 564, 3957, 591, 3957, 592, 3959, 670, 3996, 776, 3997, 891, 4008, 907, 4001, 959, 4028, 1023, 4033, 1100, 4002, 1099, 3164, 1062, 3023, 1050, 3074, 458, 3116, 461, 3950, 502], + "center":[3524, 756], + "bbox":[3022.527264265822, 457.59212376958726, 1010.0102311329347, 641.9094378926393] + }, + "NE":{ + "shape":[2971, 1616, 2976, 1616, 3755, 1662, 3820, 1709, 3848, 1690, 3931, 1693, 4010, 1732, 4020, 1761, 4043, 1765, 4044, 1765, 4045, 1765, 4054, 1768, 4059, 1827, 4074, 1860, 4089, 1865, 4099, 1898, 4095, 1948, 4121, 1965, 4115, 1986, 4127, 1995, 4119, 2014, 4131, 2018, 4126, 2040, 4136, 2071, 4125, 2102, 4143, 2131, 4142, 2133, 4159, 2139, 4159, 2184, 4185, 2197, 4195, 2236, 4211, 2244, 4206, 2243, 3243, 2215, 3215, 2213, 3228, 2022, 3006, 2005, 2937, 1999, 2971, 1616, 2971, 1616], + "center":[3612, 1930], + "bbox":[2937.015040594679, 1615.8297803697847, 1273.652496708105, 627.6938449973413] + }, + "NY":{ + "shape":[[7362, 1671, 7357, 1658, 7401, 1659, 7402, 1642, 7477, 1620, 7517, 1573, 7481, 1633, 7506, 1626, 7517, 1598, 7550, 1598, 7581, 1575, 7480, 1659, 7339, 1731, 7361, 1728, 7312, 1749, 7333, 1725, 7294, 1740, 7303, 1702, 7327, 1700, 7328, 1680, 7341, 1689, 7346, 1667, 7362, 1671], [7184, 893, 7196, 969, 7218, 999, 7216, 1076, 7241, 1126, 7239, 1162, 7255, 1151, 7267, 1166, 7302, 1314, 7302, 1327, 7304, 1452, 7307, 1462, 7331, 1595, 7345, 1608, 7315, 1637, 7331, 1657, 7330, 1657, 7320, 1695, 7303, 1699, 7293, 1722, 7292, 1722, 7297, 1663, 7175, 1625, 7167, 1623, 7121, 1609, 7103, 1589, 7094, 1550, 7066, 1546, 7046, 1524, 6427, 1652, 6418, 1601, 6418, 1601, 6494, 1530, 6527, 1480, 6507, 1445, 6511, 1430, 6486, 1428, 6481, 1394, 6608, 1347, 6657, 1344, 6692, 1358, 6744, 1336, 6778, 1344, 6770, 1334, 6866, 1255, 6865, 1239, 6837, 1205, 6869, 1168, 6846, 1180, 6855, 1163, 6841, 1160, 6834, 1184, 6817, 1160, 6877, 1095, 6883, 1064, 6934, 987, 6991, 943, 7184, 893], [7409, 1706, 7398, 1711, 7469, 1664, 7409, 1706], [7382, 1718, 7363, 1729, 7406, 1703, 7382, 1718], [7292, 1741, 7269, 1767, 7272, 1740, 7292, 1741]], + "center":[6982, 1368], + "bbox":[6417.580786242645, 893.4859395487013, 1162.9619795113686, 873.4925980295768] + }, + "GA":{ + "shape":[6143, 3060, 6144, 3060, 6113, 3123, 6176, 3156, 6176, 3156, 6196, 3157, 6258, 3247, 6374, 3325, 6375, 3348, 6410, 3379, 6457, 3400, 6481, 3470, 6523, 3496, 6545, 3561, 6579, 3568, 6580, 3568, 6593, 3574, 6582, 3589, 6576, 3573, 6575, 3590, 6562, 3577, 6582, 3595, 6574, 3607, 6549, 3590, 6554, 3602, 6539, 3599, 6557, 3608, 6523, 3597, 6568, 3616, 6555, 3636, 6545, 3622, 6548, 3636, 6521, 3624, 6546, 3641, 6534, 3650, 6555, 3641, 6558, 3655, 6553, 3669, 6540, 3655, 6547, 3669, 6522, 3676, 6555, 3676, 6543, 3705, 6528, 3694, 6543, 3716, 6509, 3714, 6527, 3718, 6526, 3731, 6527, 3721, 6548, 3726, 6534, 3752, 6522, 3740, 6520, 3761, 6492, 3747, 6524, 3780, 6493, 3792, 6528, 3792, 6524, 3814, 6507, 3812, 6526, 3836, 6507, 3840, 6508, 3840, 6456, 3827, 6438, 3838, 6448, 3917, 6427, 3924, 6411, 3885, 5968, 3918, 5939, 3866, 5939, 3865, 5916, 3831, 5919, 3762, 5896, 3708, 5902, 3649, 5927, 3621, 5907, 3611, 5911, 3591, 5866, 3512, 5757, 3137, 5751, 3113, 5772, 3111, 5944, 3089, 5953, 3088, 5983, 3084, 6140, 3060, 6143, 3060], + "center":[6159, 3499], + "bbox":[5751.066659695458, 3059.735439125348, 842.1830852324074, 864.2944778928199] + }, + "NV":{ + "shape":[1536, 1604, 1345, 2545, 1345, 2545, 1315, 2697, 1293, 2725, 1276, 2722, 1260, 2695, 1202, 2695, 1195, 2871, 1179, 2902, 1179, 2903, 553, 1964, 701, 1416, 703, 1408, 722, 1413, 1117, 1512, 1117, 1512, 1118, 1512, 1535, 1604, 1536, 1604], + "center":[1045, 1927], + "bbox":[552.6877160601398, 1407.9247770441316, 982.9101261496735, 1495.0102889725422] + }, + "TN":{ + "shape":[6012, 2768, 6017, 2767, 6319, 2725, 6319, 2725, 6322, 2773, 6293, 2785, 6278, 2822, 6229, 2830, 6202, 2866, 6186, 2847, 6165, 2877, 6147, 2879, 6140, 2910, 6114, 2916, 6067, 2961, 6009, 2978, 5990, 3001, 5989, 3028, 5952, 3042, 5953, 3088, 5944, 3089, 5772, 3111, 5751, 3113, 5342, 3152, 5341, 3152, 5014, 3177, 5009, 3177, 5045, 3148, 5028, 3122, 5041, 3100, 5026, 3095, 5038, 3082, 5048, 3098, 5046, 3067, 5065, 3073, 5057, 3059, 5073, 3047, 5055, 3032, 5093, 3013, 5083, 3000, 5102, 2996, 5089, 2979, 5089, 2978, 5107, 2950, 5089, 2931, 5113, 2927, 5100, 2914, 5115, 2907, 5110, 2881, 5114, 2880, 5118, 2880, 5118, 2882, 5129, 2880, 5140, 2878, 5339, 2863, 5334, 2828, 6011, 2769, 6012, 2768], + "center":[5533, 2967], + "bbox":[5008.8876325677875, 2724.57469597311, 1312.8105366827986, 452.62423421965195] + }, + "CA":{ + "shape":[124, 1236, 703, 1408, 701, 1416, 553, 1964, 1179, 2903, 1179, 2903, 1174, 2927, 1193, 2962, 1196, 3013, 1229, 3058, 1178, 3078, 1153, 3106, 1139, 3179, 1102, 3200, 1097, 3220, 1105, 3229, 1092, 3262, 1121, 3280, 1121, 3315, 1106, 3331, 1075, 3331, 1073, 3331, 684, 3281, 675, 3245, 689, 3269, 691, 3255, 679, 3241, 671, 3251, 680, 3184, 663, 3123, 582, 3010, 537, 3005, 544, 2987, 533, 2946, 487, 2941, 442, 2909, 390, 2833, 254, 2790, 232, 2760, 261, 2654, 231, 2628, 243, 2608, 239, 2587, 191, 2528, 176, 2457, 133, 2383, 136, 2329, 157, 2327, 180, 2295, 168, 2261, 138, 2254, 112, 2214, 108, 2130, 123, 2084, 138, 2084, 133, 2123, 182, 2166, 167, 2153, 165, 2102, 151, 2088, 158, 2072, 145, 2055, 157, 2049, 254, 2078, 272, 2065, 268, 2080, 298, 2084, 273, 2064, 243, 2069, 268, 2048, 223, 2060, 209, 2039, 220, 2032, 188, 2052, 176, 2035, 182, 2009, 177, 2043, 159, 2021, 144, 2025, 130, 2078, 99, 2053, 79, 2009, 61, 2023, 80, 1997, 79, 1979, 94, 2014, 71, 1934, 12, 1822, 24, 1799, 22, 1739, 43, 1704, 47, 1650, 0, 1548, 4, 1513, 47, 1463, 45, 1477, 72, 1455, 65, 1446, 48, 1461, 77, 1424, 78, 1397, 118, 1329, 104, 1274, 124, 1236], + "center":[507, 2428], + "bbox":[0.0, 1235.8258093785118, 1228.720840043634, 2095.1776242661253] + }, + "OK":{ + "shape":[3178, 2791, 3178, 2791, 4284, 2819, 4321, 2819, 4322, 2863, 4323, 2915, 4327, 2945, 4356, 3126, 4352, 3449, 4352, 3464, 4291, 3445, 4233, 3404, 4222, 3421, 4173, 3408, 4145, 3427, 4085, 3428, 4053, 3458, 4009, 3427, 4014, 3417, 3986, 3430, 3950, 3405, 3927, 3451, 3914, 3413, 3880, 3431, 3840, 3397, 3813, 3422, 3793, 3416, 3796, 3396, 3774, 3393, 3765, 3367, 3765, 3367, 3729, 3362, 3710, 3380, 3692, 3360, 3598, 3347, 3595, 3323, 3571, 3299, 3565, 3315, 3520, 3312, 3484, 3274, 3471, 3276, 3487, 2903, 3153, 2884, 3023, 2874, 3030, 2791, 3031, 2779, 3134, 2787, 3178, 2791], + "center":[3930, 3108], + "bbox":[3023.197942403276, 2778.8561310642554, 1332.3624136691633, 685.566148248552] + }, + "OH":{ + "shape":[5911, 1786, 5991, 1817, 6004, 1801, 5999, 1814, 6023, 1808, 5975, 1832, 6041, 1829, 6027, 1817, 6061, 1832, 6123, 1797, 6162, 1796, 6219, 1734, 6320, 1675, 6321, 1675, 6364, 1929, 6364, 1929, 6345, 1942, 6363, 1991, 6345, 2130, 6303, 2183, 6281, 2195, 6267, 2184, 6254, 2214, 6237, 2217, 6224, 2255, 6236, 2284, 6216, 2296, 6209, 2275, 6192, 2271, 6172, 2320, 6184, 2354, 6168, 2361, 6166, 2388, 6127, 2398, 6127, 2398, 6084, 2371, 6074, 2341, 6017, 2380, 5981, 2364, 5961, 2382, 5927, 2360, 5874, 2358, 5831, 2303, 5780, 2312, 5778, 2313, 5721, 1845, 5718, 1819, 5906, 1787, 5906, 1787, 5911, 1786], + "center":[6096, 2082], + "bbox":[5718.242249393261, 1674.6267669674462, 645.9704766536042, 723.8687419559174] + }, + "WY":{ + "shape":[3005, 1232, 3004, 1235, 2971, 1616, 2971, 1616, 2937, 1999, 2924, 1998, 2225, 1917, 2214, 1915, 1927, 1871, 1956, 1693, 1959, 1681, 2037, 1210, 2037, 1210, 2042, 1174, 2053, 1110, 3001, 1232, 3005, 1232], + "center":[2487, 1535], + "bbox":[1926.8321924800312, 1110.197616627383, 1077.7683497025378, 889.2322221788561] + }, + "FL":{ + "shape":[6508, 3840, 6528, 3854, 6539, 3839, 6543, 3875, 6530, 3858, 6584, 4003, 6595, 4014, 6587, 3987, 6675, 4136, 6637, 4084, 6664, 4136, 6750, 4223, 6762, 4245, 6748, 4256, 6756, 4285, 6793, 4358, 6731, 4264, 6724, 4233, 6740, 4221, 6739, 4270, 6756, 4303, 6745, 4219, 6711, 4219, 6718, 4202, 6694, 4187, 6727, 4263, 6862, 4481, 6836, 4471, 6851, 4487, 6868, 4482, 6890, 4522, 6876, 4517, 6902, 4547, 6896, 4539, 6921, 4741, 6919, 4751, 6915, 4716, 6894, 4786, 6893, 4872, 6861, 4866, 6868, 4874, 6848, 4887, 6819, 4884, 6773, 4906, 6755, 4887, 6756, 4867, 6785, 4885, 6801, 4874, 6754, 4856, 6738, 4832, 6753, 4810, 6734, 4825, 6719, 4803, 6735, 4792, 6720, 4783, 6716, 4801, 6704, 4777, 6667, 4764, 6694, 4777, 6657, 4762, 6635, 4770, 6631, 4759, 6645, 4756, 6617, 4739, 6597, 4668, 6567, 4666, 6603, 4617, 6562, 4660, 6540, 4586, 6556, 4557, 6532, 4585, 6506, 4569, 6535, 4610, 6511, 4595, 6510, 4610, 6447, 4528, 6443, 4503, 6421, 4493, 6465, 4477, 6428, 4479, 6458, 4445, 6457, 4394, 6438, 4420, 6436, 4399, 6419, 4396, 6429, 4392, 6403, 4383, 6417, 4395, 6402, 4404, 6432, 4409, 6420, 4418, 6423, 4447, 6386, 4425, 6408, 4454, 6382, 4422, 6398, 4307, 6394, 4244, 6383, 4235, 6393, 4239, 6391, 4218, 6367, 4201, 6354, 4168, 6314, 4174, 6291, 4153, 6298, 4145, 6244, 4116, 6240, 4089, 6213, 4079, 6187, 4044, 6127, 4016, 6093, 4018, 6065, 4038, 6073, 4051, 6055, 4046, 6075, 4064, 6044, 4065, 5988, 4110, 5992, 4091, 5970, 4113, 5910, 4130, 5896, 4096, 5909, 4127, 5913, 4096, 5845, 4047, 5883, 4064, 5895, 4056, 5836, 4037, 5868, 4007, 5861, 3999, 5838, 4026, 5828, 4011, 5810, 4015, 5816, 4030, 5830, 4026, 5836, 4049, 5781, 4023, 5700, 4011, 5768, 4007, 5751, 3984, 5715, 3998, 5701, 3986, 5687, 4010, 5589, 4032, 5639, 4008, 5613, 3998, 5612, 3979, 5604, 4010, 5584, 3985, 5589, 4017, 5532, 4051, 5561, 4019, 5545, 4009, 5545, 4009, 5548, 3975, 5506, 3942, 5508, 3912, 5910, 3869, 5939, 3865, 5939, 3866, 5968, 3918, 6411, 3885, 6427, 3924, 6448, 3917, 6438, 3838, 6456, 3827, 6508, 3840], + "center":[6585, 4335], + "bbox":[5506.310458865219, 3827.186951564696, 1414.2469623466595, 1078.9271397334787] + }, + "SD":{ + "shape":[3023, 1050, 3164, 1062, 4002, 1099, 4033, 1100, 4029, 1123, 3992, 1163, 4047, 1222, 4045, 1570, 4025, 1570, 4035, 1591, 4026, 1613, 4047, 1642, 4018, 1718, 4044, 1765, 4043, 1765, 4020, 1761, 4010, 1732, 3931, 1693, 3848, 1690, 3820, 1709, 3755, 1662, 2976, 1616, 2971, 1616, 3004, 1235, 3005, 1232, 3022, 1057, 3023, 1050], + "center":[3544, 1347], + "bbox":[2971.124373651464, 1050.043615899464, 1075.661674971776, 715.2386540159926] + }, + "SC":{ + "shape":[6176, 3156, 6113, 3123, 6144, 3060, 6143, 3060, 6249, 3003, 6461, 2982, 6465, 3002, 6480, 2987, 6509, 3016, 6511, 3038, 6686, 3010, 6891, 3152, 6891, 3152, 6826, 3243, 6815, 3270, 6821, 3298, 6801, 3279, 6812, 3254, 6800, 3266, 6799, 3286, 6821, 3302, 6814, 3316, 6806, 3304, 6812, 3316, 6795, 3313, 6807, 3317, 6785, 3340, 6797, 3341, 6769, 3342, 6758, 3362, 6768, 3367, 6729, 3404, 6719, 3386, 6734, 3368, 6716, 3392, 6709, 3373, 6716, 3402, 6698, 3393, 6727, 3408, 6714, 3429, 6706, 3421, 6710, 3436, 6683, 3450, 6667, 3441, 6675, 3421, 6658, 3443, 6680, 3450, 6661, 3470, 6641, 3436, 6651, 3472, 6623, 3460, 6628, 3470, 6597, 3471, 6639, 3481, 6630, 3502, 6646, 3486, 6647, 3504, 6618, 3520, 6608, 3485, 6612, 3514, 6573, 3474, 6591, 3506, 6576, 3517, 6615, 3529, 6594, 3555, 6596, 3527, 6582, 3532, 6595, 3539, 6579, 3568, 6579, 3568, 6545, 3561, 6523, 3496, 6481, 3470, 6457, 3400, 6410, 3379, 6375, 3348, 6374, 3325, 6258, 3247, 6196, 3157, 6176, 3156, 6176, 3156], + "center":[6541, 3231], + "bbox":[6112.614477369485, 2981.961131844549, 778.5946861628072, 586.2283645094535] + }, + "CT":{ + "shape":[7541, 1398, 7568, 1526, 7567, 1528, 7539, 1536, 7531, 1523, 7535, 1539, 7521, 1547, 7504, 1553, 7491, 1539, 7496, 1559, 7426, 1581, 7424, 1569, 7401, 1605, 7331, 1655, 7331, 1657, 7315, 1637, 7345, 1608, 7331, 1595, 7307, 1462, 7304, 1452, 7321, 1448, 7541, 1398], + "center":[7442, 1505], + "bbox":[7304.222285771861, 1397.8167615354203, 263.7757107347343, 259.100971032168] + }, + "WV":{ + "shape":[6364, 1929, 6369, 1959, 6394, 2102, 6546, 2075, 6549, 2091, 6565, 2173, 6610, 2111, 6632, 2114, 6652, 2069, 6669, 2087, 6701, 2086, 6702, 2065, 6726, 2061, 6736, 2044, 6790, 2051, 6785, 2062, 6804, 2069, 6817, 2100, 6817, 2101, 6809, 2139, 6720, 2092, 6723, 2150, 6670, 2239, 6647, 2227, 6621, 2315, 6560, 2293, 6550, 2358, 6492, 2481, 6507, 2491, 6493, 2505, 6500, 2512, 6479, 2534, 6467, 2527, 6433, 2553, 6418, 2545, 6415, 2567, 6368, 2591, 6344, 2575, 6301, 2608, 6258, 2584, 6247, 2552, 6247, 2551, 6222, 2552, 6212, 2535, 6192, 2533, 6155, 2489, 6159, 2478, 6128, 2452, 6127, 2399, 6127, 2398, 6166, 2388, 6168, 2361, 6184, 2354, 6172, 2320, 6192, 2271, 6209, 2275, 6216, 2296, 6236, 2284, 6224, 2255, 6237, 2217, 6254, 2214, 6267, 2184, 6281, 2195, 6303, 2183, 6345, 2130, 6363, 1991, 6345, 1942, 6364, 1929, 6364, 1929], + "center":[6378, 2332], + "bbox":[6127.105822495098, 1928.5641252578148, 690.343968832166, 679.5274353689069] + }, + "DC":{ + "shape":[6928, 2160, 6921, 2156, 6921, 2156, 6924, 2150, 6930, 2142, 6953, 2157, 6940, 2177, 6939, 2177, 6944, 2161, 6928, 2160], + "center":[6934, 2153], + "bbox":[6920.506533181993, 2141.589247282631, 32.58943373685452, 35.901722553833224] + }, + "WI":{ + "shape":[[5338, 1169, 5315, 1247, 5302, 1264, 5292, 1258, 5286, 1242, 5305, 1190, 5314, 1193, 5326, 1164, 5338, 1169], [4702, 927, 4791, 882, 4806, 895, 4783, 954, 4818, 940, 4808, 931, 4855, 954, 4855, 954, 4883, 965, 4898, 996, 5176, 1057, 5173, 1078, 5222, 1098, 5217, 1122, 5225, 1135, 5213, 1163, 5244, 1157, 5236, 1192, 5255, 1208, 5256, 1209, 5255, 1232, 5227, 1244, 5210, 1289, 5214, 1313, 5205, 1316, 5221, 1322, 5264, 1256, 5284, 1246, 5301, 1264, 5282, 1331, 5285, 1384, 5268, 1401, 5261, 1434, 5268, 1483, 5246, 1566, 5251, 1612, 5276, 1656, 5275, 1711, 5275, 1711, 4872, 1739, 4871, 1739, 4860, 1714, 4809, 1695, 4789, 1625, 4804, 1599, 4782, 1580, 4779, 1554, 4779, 1552, 4772, 1499, 4744, 1458, 4690, 1430, 4667, 1391, 4614, 1357, 4585, 1355, 4548, 1321, 4551, 1218, 4566, 1188, 4550, 1165, 4532, 1162, 4534, 1135, 4558, 1095, 4609, 1063, 4604, 948, 4629, 930, 4629, 932, 4649, 944, 4702, 927]], + "center":[4969, 1326], + "bbox":[4532.3918561497785, 881.6938347091063, 805.2276107605994, 857.1162210960997] + }, + "KY":{ + "shape":[[5118, 2880, 5110, 2881, 5109, 2880, 5110, 2865, 5118, 2880], [5778, 2313, 5780, 2312, 5831, 2303, 5874, 2358, 5927, 2360, 5961, 2382, 5981, 2364, 6017, 2380, 6074, 2341, 6084, 2371, 6127, 2398, 6127, 2398, 6127, 2399, 6128, 2452, 6159, 2478, 6155, 2489, 6192, 2533, 6212, 2535, 6222, 2552, 6247, 2551, 6247, 2552, 6230, 2571, 6144, 2648, 6127, 2694, 6098, 2707, 6091, 2729, 6012, 2768, 6011, 2769, 5334, 2828, 5339, 2863, 5140, 2878, 5129, 2880, 5137, 2853, 5160, 2860, 5159, 2831, 5171, 2825, 5161, 2817, 5167, 2783, 5166, 2783, 5164, 2782, 5158, 2769, 5185, 2735, 5266, 2759, 5261, 2698, 5320, 2674, 5305, 2642, 5322, 2614, 5322, 2613, 5323, 2613, 5340, 2610, 5340, 2587, 5383, 2602, 5385, 2574, 5462, 2603, 5472, 2578, 5501, 2558, 5531, 2585, 5550, 2566, 5553, 2530, 5574, 2523, 5567, 2513, 5585, 2539, 5620, 2553, 5637, 2538, 5645, 2489, 5666, 2484, 5675, 2455, 5701, 2433, 5695, 2396, 5734, 2399, 5786, 2374, 5788, 2355, 5773, 2350, 5779, 2338, 5768, 2324, 5777, 2313, 5778, 2313]], + "center":[5828, 2581], + "bbox":[5109.050661398359, 2303.369688273179, 1137.790998421695, 577.2572832752585] + }, + "KS":{ + "shape":[3215, 2213, 3243, 2215, 4206, 2243, 4211, 2244, 4210, 2245, 4242, 2269, 4266, 2264, 4277, 2292, 4262, 2293, 4241, 2331, 4274, 2360, 4282, 2393, 4319, 2406, 4321, 2819, 4284, 2819, 3178, 2791, 3178, 2791, 3210, 2296, 3215, 2213], + "center":[3753, 2506], + "bbox":[3177.7941461662417, 2213.4066104839408, 1143.4447618402305, 605.7450113212822] + }, + "OR":{ + "shape":[477, 509, 504, 509, 532, 539, 534, 584, 521, 593, 534, 585, 521, 632, 526, 623, 577, 662, 633, 657, 633, 657, 691, 657, 734, 693, 810, 686, 863, 705, 955, 690, 1008, 704, 1037, 696, 1297, 762, 1309, 764, 1309, 766, 1319, 800, 1346, 822, 1352, 851, 1276, 949, 1267, 976, 1234, 998, 1194, 1057, 1195, 1080, 1231, 1110, 1197, 1168, 1117, 1512, 1117, 1512, 722, 1413, 703, 1408, 124, 1236, 124, 1235, 110, 1211, 113, 1167, 135, 1113, 125, 1067, 178, 985, 186, 997, 204, 973, 210, 988, 212, 967, 187, 981, 223, 930, 236, 921, 261, 943, 237, 918, 223, 929, 252, 869, 266, 879, 253, 868, 283, 797, 294, 802, 283, 796, 297, 762, 310, 772, 297, 760, 306, 728, 347, 665, 359, 605, 376, 607, 367, 590, 384, 570, 375, 575, 375, 554, 385, 520, 399, 511, 399, 466, 421, 501, 415, 480, 464, 485, 477, 509], + "center":[746, 1019], + "bbox":[110.42702484968872, 466.18654659455024, 1241.1399115078336, 1045.3897533373636] + }, + "LA":{ + "shape":[4426, 3583, 4464, 3582, 4879, 3569, 4894, 3568, 4894, 3569, 4889, 3586, 4903, 3571, 4912, 3587, 4897, 3615, 4915, 3623, 4900, 3641, 4922, 3637, 4913, 3654, 4928, 3664, 4910, 3656, 4908, 3669, 4932, 3675, 4928, 3692, 4948, 3689, 4931, 3722, 4902, 3724, 4903, 3736, 4929, 3733, 4918, 3752, 4905, 3747, 4918, 3759, 4892, 3798, 4877, 3794, 4873, 3810, 4891, 3811, 4872, 3813, 4867, 3838, 4850, 3837, 4869, 3845, 4850, 3857, 4858, 3885, 4842, 3879, 4853, 3903, 4831, 3910, 4848, 3948, 4836, 3959, 5153, 3940, 5138, 4007, 5183, 4090, 5199, 4096, 5199, 4096, 5184, 4098, 5169, 4129, 5151, 4128, 5149, 4144, 5187, 4158, 5192, 4133, 5214, 4118, 5210, 4139, 5227, 4140, 5214, 4144, 5226, 4153, 5217, 4151, 5220, 4166, 5236, 4172, 5224, 4178, 5205, 4161, 5201, 4170, 5216, 4168, 5205, 4182, 5181, 4182, 5214, 4204, 5175, 4191, 5191, 4208, 5164, 4211, 5210, 4237, 5204, 4249, 5243, 4248, 5231, 4254, 5255, 4255, 5255, 4268, 5266, 4253, 5282, 4271, 5277, 4286, 5298, 4277, 5284, 4290, 5298, 4291, 5293, 4306, 5277, 4294, 5281, 4325, 5260, 4300, 5236, 4340, 5256, 4293, 5246, 4288, 5249, 4301, 5235, 4307, 5222, 4276, 5187, 4272, 5197, 4273, 5197, 4253, 5179, 4236, 5195, 4263, 5159, 4269, 5171, 4258, 5160, 4239, 5102, 4232, 5106, 4203, 5101, 4219, 5092, 4198, 5101, 4216, 5089, 4233, 5120, 4242, 5125, 4273, 5111, 4266, 5122, 4296, 5087, 4319, 5091, 4288, 5070, 4275, 5065, 4289, 5054, 4267, 5049, 4281, 5041, 4267, 5043, 4282, 5026, 4277, 5031, 4297, 5013, 4285, 5023, 4312, 5012, 4300, 5017, 4314, 5001, 4319, 4987, 4304, 4982, 4317, 4972, 4309, 4996, 4297, 4991, 4288, 4948, 4273, 4939, 4285, 4912, 4244, 4864, 4246, 4862, 4223, 4846, 4224, 4858, 4201, 4810, 4212, 4813, 4188, 4751, 4205, 4770, 4215, 4769, 4232, 4786, 4231, 4740, 4249, 4582, 4207, 4476, 4226, 4475, 4227, 4459, 4204, 4497, 4154, 4488, 4061, 4524, 3965, 4519, 3935, 4492, 3912, 4500, 3900, 4481, 3881, 4487, 3873, 4467, 3859, 4468, 3822, 4431, 3781, 4426, 3596, 4426, 3583], + "center":[4688, 3984], + "bbox":[4426.055011069621, 3568.2982314605933, 872.2791142400201, 771.7208876645427] + }, + "WA":{ + "shape":[717, 0, 1411, 188, 1428, 192, 1306, 692, 1315, 731, 1304, 745, 1309, 764, 1297, 762, 1037, 696, 1008, 704, 955, 690, 863, 705, 810, 686, 734, 693, 691, 657, 633, 657, 633, 656, 586, 660, 539, 635, 528, 620, 541, 567, 535, 534, 510, 509, 483, 507, 446, 465, 392, 456, 416, 389, 407, 438, 415, 443, 422, 427, 431, 442, 422, 423, 431, 419, 429, 393, 435, 403, 437, 387, 456, 393, 444, 375, 417, 370, 422, 336, 433, 355, 435, 342, 468, 337, 434, 312, 419, 331, 430, 294, 436, 171, 419, 131, 423, 80, 442, 51, 436, 39, 446, 40, 517, 109, 605, 147, 627, 139, 615, 143, 630, 173, 629, 161, 645, 163, 644, 184, 653, 176, 650, 163, 668, 160, 659, 170, 672, 214, 660, 214, 639, 245, 646, 213, 638, 225, 637, 215, 622, 247, 572, 293, 617, 290, 579, 287, 610, 257, 641, 250, 671, 219, 674, 228, 674, 204, 684, 212, 682, 245, 671, 239, 664, 251, 659, 239, 657, 271, 644, 262, 652, 273, 641, 278, 663, 271, 665, 289, 645, 330, 633, 326, 635, 339, 627, 323, 643, 302, 626, 310, 612, 342, 618, 296, 592, 328, 572, 321, 591, 330, 568, 344, 593, 335, 575, 358, 590, 339, 587, 359, 592, 341, 602, 340, 601, 353, 605, 338, 618, 358, 648, 322, 659, 340, 660, 328, 678, 325, 678, 279, 690, 296, 681, 263, 713, 214, 735, 203, 717, 185, 721, 157, 705, 165, 713, 192, 698, 171, 702, 149, 726, 146, 709, 126, 712, 111, 696, 115, 695, 97, 720, 113, 717, 86, 735, 88, 728, 72, 736, 57, 712, 54, 718, 45, 706, 19, 714, 13, 706, 8, 717, 0], + "center":[952, 376], + "bbox":[391.5842598432282, 0.0, 1036.0850727961474, 764.2512207821578] + }, + "CO":{ + "shape":[2214, 1915, 2225, 1917, 2924, 1998, 2937, 1999, 3006, 2005, 3228, 2022, 3215, 2213, 3210, 2296, 3178, 2791, 3134, 2787, 3031, 2779, 3017, 2778, 2182, 2687, 2105, 2676, 2213, 1925, 2214, 1915], + "center":[2749, 2301], + "bbox":[2104.8628776804103, 1915.455020807188, 1122.9312587549507, 875.2906417521679] + }, + "PA":{ + "shape":[6321, 1675, 6417, 1602, 6418, 1601, 6427, 1652, 7046, 1524, 7066, 1546, 7094, 1550, 7103, 1589, 7121, 1609, 7167, 1623, 7166, 1623, 7122, 1706, 7138, 1727, 7125, 1764, 7132, 1786, 7151, 1788, 7157, 1810, 7217, 1848, 7133, 1938, 7132, 1937, 7100, 1940, 7082, 1965, 7050, 1972, 6559, 2073, 6546, 2075, 6394, 2102, 6369, 1959, 6364, 1929, 6321, 1675], + "center":[6777, 1802], + "bbox":[6320.668447791168, 1524.3096158541616, 896.2956547030162, 577.9314409361236] + } + } +} diff --git a/js/dojo/dojox/geo/charting/resources/data/series.json b/js/dojo/dojox/geo/charting/resources/data/series.json new file mode 100644 index 0000000..7d0b807 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/data/series.json @@ -0,0 +1,20 @@ +{ + "series": [{ + name: "Low sales state(0~$3.0M)", + min: "0.0", + max: "3.0", + color: "#FFCE52" + }, + { + name: "Normal sales state($3.0M~$6.0M)", + min: "3.0", + max: "6.0", + color: "#63A584" + }, + { + name: "High sales state($6.0M~$10.0M)", + min: "6.0", + max: "9.0", + color: "#CE6342" + }] +} diff --git a/js/dojo/dojox/geo/charting/resources/img/zoomin.gif b/js/dojo/dojox/geo/charting/resources/img/zoomin.gif Binary files differnew file mode 100644 index 0000000..45fc12b --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/img/zoomin.gif diff --git a/js/dojo/dojox/geo/charting/resources/img/zoomin.png b/js/dojo/dojox/geo/charting/resources/img/zoomin.png Binary files differnew file mode 100644 index 0000000..fed568a --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/img/zoomin.png diff --git a/js/dojo/dojox/geo/charting/resources/img/zoomout.gif b/js/dojo/dojox/geo/charting/resources/img/zoomout.gif Binary files differnew file mode 100644 index 0000000..780b747 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/img/zoomout.gif diff --git a/js/dojo/dojox/geo/charting/resources/img/zoomout.png b/js/dojo/dojox/geo/charting/resources/img/zoomout.png Binary files differnew file mode 100644 index 0000000..f62af36 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/img/zoomout.png diff --git a/js/dojo/dojox/geo/charting/resources/markers/USStates.json b/js/dojo/dojox/geo/charting/resources/markers/USStates.json new file mode 100644 index 0000000..929dab3 --- /dev/null +++ b/js/dojo/dojox/geo/charting/resources/markers/USStates.json @@ -0,0 +1 @@ +{"AK":"Alaska","AZ":"Arizona","AR":"Arkansas","AL":"Alabama","CA":"California","CO":"Colorado","CT":"Connecticut","DE":"Delaware","FL":"Florida","GA":"Georgia","HI":"Hawaii","ID":"Idaho","IL":"Illinois","IN":"Indiana","IA":"Iowa","KS":"Kansas","KY":"Kentucky","LA":"Louisiana","ME":"Maine","MD":"Maryland","MA":"Massachusetts","MI":"Michigan","MN":"Minnesota","MS":"Mississippi","MO":"Missouri","MT":"Montana","NE":"Nebraska","NV":"Nevada","NH":"New Hampshire","NJ":"New Jersey","NM":"New Mexico","NY":"New York","NC":"North Carolina","ND":"North Dakota","OH":"Ohio","OK":"Oklahoma","OR":"Oregon","PA":"Pennsylvania","RI":"Rhode Island","SC":"South Carolina","SD":"South Dakota","TN":"Tennessee","TX":"Texas","UT":"Utah","VT":"Vermont","VA":"Virginia","WA":"Washington","WV":"West Virginia","WI":"Wisconsin","WY":"Wyoming"} diff --git a/js/dojo/dojox/geo/charting/widget/Legend.js b/js/dojo/dojox/geo/charting/widget/Legend.js new file mode 100644 index 0000000..f307d77 --- /dev/null +++ b/js/dojo/dojox/geo/charting/widget/Legend.js @@ -0,0 +1,93 @@ +//>>built + +define("dojox/geo/charting/widget/Legend", ["dojo/_base/kernel", "dojo/_base/lang","dojo/_base/array", "dojo/_base/declare","dojo/_base/html","dojo/dom", + "dojo/dom-construct","dojo/dom-class", "dojo/_base/window", "dijit/_Widget"], + function(dojo, lang, arr, declare, html,dom,domConstruct,domClass, win, Widget) { + +return declare("dojox.geo.charting.widget.Legend",Widget, { + // summary: + // A legend widget displaying association between colors and Feature value ranges. + // + // description: + // This widget basically is a table comprising (icon,string) pairs, describing the color scheme + // used for the map and its associated text descriptions. + // + + // example: + // | var legend = new dojox.geo.charting.widget.Legend({ + // | map: map + // | }); + horizontal:true, + legendBody:null, + swatchSize:18, + map:null, + postCreate: function(){ + // summary: + // inherited Dijit's postCreate function + // tags: + // protected + if(!this.map){return;} + this.series = this.map.series; + if (!this.domNode.parentNode) { + // compatibility with older version : add to map domNode if not already attached to a parentNode. + dom.byId(this.map.container).appendChild(this.domNode); + } + this.refresh(); + }, + buildRendering: function(){ + // summary: + // Construct the UI for this widget, creates the underlying real dojox.geo.charting.Map object. + // tags: + // protected + this.domNode = domConstruct.create("table", + {role: "group", "class": "dojoxLegendNode"}); + this.legendBody = domConstruct.create("tbody", null, this.domNode); + this.inherited(arguments); + }, + + refresh:function(){ + // summary: + // Refreshes this legend contents when Map series has changed. + // cleanup + while(this.legendBody.lastChild){ + domConstruct.destroy(this.legendBody.lastChild); + } + + if(this.horizontal){ + domClass.add(this.domNode,"dojoxLegendHorizontal"); + this._tr = win.doc.createElement("tr"); + this.legendBody.appendChild(this._tr); + } + + var s = this.series; + if(s.length == 0){return;} + + arr.forEach(s,function(x){ + this._addLabel(x.color, x.name); + },this); + }, + _addLabel:function(color,label){ + var icon = win.doc.createElement("td"); + var text = win.doc.createElement("td"); + var div = win.doc.createElement("div"); + domClass.add(icon, "dojoxLegendIcon"); + domClass.add(text, "dojoxLegendText"); + div.style.width = this.swatchSize + "px"; + div.style.height = this.swatchSize + "px"; + icon.appendChild(div); + + if(this.horizontal){ + this._tr.appendChild(icon); + this._tr.appendChild(text); + }else{ + var tr = win.doc.createElement("tr"); + this.legendBody.appendChild(tr); + tr.appendChild(icon); + tr.appendChild(text); + } + + div.style.background = color; + text.innerHTML = String(label); + } +}); +}); diff --git a/js/dojo/dojox/geo/charting/widget/Map.js b/js/dojo/dojox/geo/charting/widget/Map.js new file mode 100644 index 0000000..dc28e7d --- /dev/null +++ b/js/dojo/dojox/geo/charting/widget/Map.js @@ -0,0 +1,182 @@ +//>>built + +define("dojox/geo/charting/widget/Map", ["dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/declare","dojo/_base/html","dojo/dom-geometry", + "dijit/_Widget","dojox/geo/charting/Map"], + function(dojo, lang, declare, html,domGeom, Widget, Map) { + +return declare("dojox.geo.charting.widget.Map", Widget, { + // summary: + // A map viewer widget based on the dojox.geo.charting.Map component + // + // description: + // The `dojox.geo.charting.widget.Map` widget combines map display together with charting capabilities. + // It encapsulates an `dojox.geo.charting.Map` object on which most operations are delegated. + // Parameters can be passed as argument at construction time to specify map data file (json shape format) + // as well as charting data. + // + // The parameters are : + // + // * `shapeData`: The json object containing map data or the name of the file containing map data. + // * `dataStore`: the dataStore to fetch the charting data from + // * `dataBindingAttribute`: property name of the dataStore items to use as value for charting + // * `markerData`: tooltips to display for map features, handled as json style. + // * `adjustMapCenterOnResize`: if true, the center of the map remains the same when resizing the widget + // * `adjustMapScaleOnResize`: if true, the map scale is adjusted to leave the visible portion of the map identical as much as possible + // + // example: + // + // | var map = new dojox.geo.charting.widget.Map({ + // | shapeData : 'map.json', + // | adjustMapCenterOnresize : true, + // | adjustMapScaleOnresize : true, + // | }); + + shapeData : "", + dataStore : null, + dataBindingAttribute : "", + dataBindingValueFunction: null, + markerData : "", + series : "", + adjustMapCenterOnResize: null, + adjustMapScaleOnResize: null, + animateOnResize: null, + onFeatureClick: null, + onFeatureOver: null, + enableMouseSupport: null, + enableTouchSupport: null, + enableMouseZoom: null, + enableMousePan: null, + enableKeyboardSupport: false, + showTooltips: false, + enableFeatureZoom: null, + colorAnimationDuration: 0, + mouseClickThreshold: 2, + _mouseInteractionSupport:null, + _touchInteractionSupport:null, + _keyboardInteractionSupport:null, + constructor : function(/* Object */options, /* HtmlNode */div){ + // summary: + // Constructs a new Map widget + this.map = null; + }, + + startup : function(){ + this.inherited(arguments); + if (this.map) { + this.map.fitToMapContents(); + } + + }, + + postMixInProperties : function(){ + this.inherited(arguments); + }, + + create : function(/*Object?*/params, /*DomNode|String?*/srcNodeRef){ + this.inherited(arguments); + }, + + getInnerMap: function() { + return this.map; + }, + + + buildRendering : function(){ + // summary: + // Construct the UI for this widget, creates the underlying real dojox.geo.charting.Map object. + // tags: + // protected + this.inherited(arguments); + if (this.shapeData) { + this.map = new Map(this.domNode, this.shapeData); + if (this.markerData && (this.markerData.length > 0)) + this.map.setMarkerData(this.markerData); + + if (this.dataStore) { + if (this.dataBindingValueFunction) { + this.map.setDataBindingValueFunction(this.dataBindingValueFunction); + } + this.map.setDataStore(this.dataStore,this.dataBindingAttribute); + } + + if (this.series && (this.series.length > 0)) { + this.map.addSeries(this.series); + } + + if (this.onFeatureClick) { + this.map.onFeatureClick = this.onFeatureClick; + } + if (this.onFeatureOver) { + this.map.onFeatureOver = this.onFeatureOver; + } + if (this.enableMouseSupport) { + + if (!dojox.geo.charting.MouseInteractionSupport) { + throw Error("Can't find dojox.geo.charting.MouseInteractionSupport. Didn't you forget to dojo" + ".require() it?"); + } + var options = {}; + options.enablePan = this.enableMousePan; + options.enableZoom = this.enableMouseZoom; + options.mouseClickThreshold = this.mouseClickThreshold; + this._mouseInteractionSupport = new dojox.geo.charting.MouseInteractionSupport(this.map,options); + this._mouseInteractionSupport.connect(); + } + + if (this.enableTouchSupport) { + if (!dojox.geo.charting.TouchInteractionSupport) { + throw Error("Can't find dojox.geo.charting.TouchInteractionSupport. Didn't you forget to dojo" + ".require() it?"); + } + this._touchInteractionSupport = new dojox.geo.charting.TouchInteractionSupport(this.map,{}); + this._touchInteractionSupport.connect(); + } + if (this.enableKeyboardSupport) { + if (!dojox.geo.charting.KeyboardInteractionSupport) { + throw Error("Can't find dojox.geo.charting.KeyboardInteractionSupport. Didn't you forget to dojo" + ".require() it?"); + } + this._keyboardInteractionSupport = new dojox.geo.charting.KeyboardInteractionSupport(this.map,{}); + this._keyboardInteractionSupport.connect(); + } + this.map.showTooltips = this.showTooltips; + this.map.enableFeatureZoom = this.enableFeatureZoom; + this.map.colorAnimationDuration = this.colorAnimationDuration; + + + } + }, + + + resize : function(b){ + // summary: + // Resize the widget. + // description: + // Resize the domNode and the widget to the dimensions of a box of the following form: + // `{ l: 50, t: 200, w: 300: h: 150 }` + // box: + // If passed, denotes the new size of the widget. + + var box; + switch (arguments.length) { + case 0: + // case 0, do not resize the div, just the surface + break; + case 1: + // argument, override node box + box = lang.mixin({}, b); + domGeom.getMarginBox(this.domNode, box); + break; + case 2: + // two argument, width, height + box = { + w : arguments[0], + h : arguments[1] + }; + domGeom.getMarginBox(this.domNode, box); + break; + } + + if (this.map) { + this.map.resize(this.adjustMapCenterOnResize,this.adjustMapScaleOnResize,this.animateOnResize); + } + } +}); +}); diff --git a/js/dojo/dojox/geo/openlayers/Collection.js b/js/dojo/dojox/geo/openlayers/Collection.js new file mode 100644 index 0000000..25983b2 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Collection.js @@ -0,0 +1,28 @@ +//>>built +define("dojox/geo/openlayers/Collection", [ "dojo/_base/kernel", "dojo/_base/declare", "dojox/geo/openlayers/Geometry" ], + function(dojo, declare, Geometry) { + /*===== + var Geometry = dojox.geo.openlayers.Geometry; + =====*/ + return declare("dojox.geo.openlayers.Collection", Geometry, { + // summary: + // A collection of geometries. _coordinates_ holds an array of + // geometries. + + setGeometries : function(/* Array */g) { + // summary: + // Sets the geometries + // g: Array + // The array of geometries. + this.coordinates = g; + }, + + // summary: + // Retrieves the geometries. + // returns: Array + // The array of geometries defining this collection. + getGeometries : function() { + return this.coordinates; + } + }); + }); diff --git a/js/dojo/dojox/geo/openlayers/Feature.js b/js/dojo/dojox/geo/openlayers/Feature.js new file mode 100644 index 0000000..d6a37ca --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Feature.js @@ -0,0 +1,79 @@ +//>>built +define("dojox/geo/openlayers/Feature", ["dojo/_base/kernel", "dojo/_base/declare", "dojox/geo/openlayers/Map"], function(dojo, declare, Map){ + + return declare("dojox.geo.openlayers.Feature", null, { + // summary: + // A Feature encapsulates an item so that it can be added to a Layer. + // This class is not attended to be used as it, but serve as a base class + // for specific features such as GeometryFeature which can display georeferenced + // geometries and WidgetFeature which can display georeferenced widgets. + constructor : function(){ + // summary: + // Construct a new Feature + this._layer = null; + this._coordSys = dojox.geo.openlayers.EPSG4326; + }, + + getCoordinateSystem : function(){ + // summary: + // Returns the coordinate system in which coordinates of this feature are expressed. + // returns: OpenLayers.Projection + // The coordinate system in which coordinates of this feature are expressed. + return this._coordSys; + }, + + setCoordinateSystem : function(/* OpenLayers.Projection */cs){ + // summary: + // Set the coordinate system in which coordinates of this feature are expressed. + // cs: OpenLayers.Projection + // The coordinate system in which coordinates of this feature are expressed. + this._coordSys = cs; + }, + + getLayer : function(){ + // summary: + // Returns the Layer to which this feature belongs. + // returns: dojox.geo.openlayers.Layer + // The layer to which this feature belongs. + return this._layer; + }, + + _setLayer : function(/* dojox.geo.openlayers.Layer */l){ + // summary: + // Sets the layer to which this Feature belongs + // description: + // Called when the feature is added to the Layer. + // tags: + // private + this._layer = l; + }, + + render : function(){ + // summary: + // subclasses implements drawing specific behavior. + }, + + remove : function(){ + // summary: + // Subclasses implements specific behavior. + // Called when removed from the layer. + }, + + _getLocalXY : function(p){ + // summary: + // From projected coordinates to screen coordinates + // p: Object + // Object with x and y fields + // tags: + // private + var x = p.x; + var y = p.y; + var layer = this.getLayer(); + var resolution = layer.olLayer.map.getResolution(); + var extent = layer.olLayer.getExtent(); + var rx = (x / resolution + (-extent.left / resolution)); + var ry = ((extent.top / resolution) - y / resolution); + return [rx, ry]; + } + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/Geometry.js b/js/dojo/dojox/geo/openlayers/Geometry.js new file mode 100644 index 0000000..8905dee --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Geometry.js @@ -0,0 +1,37 @@ +//>>built +define("dojox/geo/openlayers/Geometry", ["dojo/_base/kernel", "dojo/_base/declare"], function(dojo, declare){ + + return declare("dojox.geo.openlayers.Geometry", null, { + // summary: + // A Geometry handles description of shapes to be rendered in a GfxLayer + // using a GeometryFeature feature. + // A Geometry can be + // - A point geometry of type dojox.geo.openlayers.Point. Coordinates are a an + // Object {x, y} + // - A line string geometry of type dojox.geo.openlayers.LineString. Coordinates are + // an array of {x, y} objects + // - A collection geometry of type dojox.geo.openlayers.Collection. Coordinates are an array of geometries. + + // summary: + // The coordinates of the geometry. + // coordinates: {x, y} | Array + coordinates : null, + + // summary: + // The associated shape when rendered + // shape : dojox.gfx.Shape + // The shape + // tags: + // internal + shape : null, + + constructor : function(coords){ + // summary: + // Constructs a new geometry + // coords: {x, y} + // Coordinates of the geometry. {x:<x>, y:<y>} object for a point geometry, array of {x:<x>, y:<y>} + // objects for line string geometry, array of geometries for collection geometry. + this.coordinates = coords; + } + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/GeometryFeature.js b/js/dojo/dojox/geo/openlayers/GeometryFeature.js new file mode 100644 index 0000000..ab75ffe --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/GeometryFeature.js @@ -0,0 +1,405 @@ +//>>built +define("dojox/geo/openlayers/GeometryFeature", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/lang", + "dojox/gfx/matrix", + "dojox/geo/openlayers/Point", + "dojox/geo/openlayers/LineString", + "dojox/geo/openlayers/Collection", + "dojox/geo/openlayers/Feature"], function(dojo, declare, array, lang, matrix, Point, LineString, + Collection, Feature){ + /*===== + var Feature = dojox.geo.openlayers.Feature; + =====*/ + return declare("dojox.geo.openlayers.GeometryFeature", Feature, { + // summary: + // A Feature encapsulating a geometry. + // description: + // This Feature renders a geometry such as a Point or LineString geometry. This Feature + // is responsible for reprojecting the geometry before creating a gfx shape to display it. + // By default the shape created is a circle for a Point geometry and a polyline for a + // LineString geometry. User can change these behavior by overriding the createShape + // method to create the desired shape. + // example: + // | var geom = new dojox.geo.openlayers.Point({x:0, y:0}); + // | var gf = new dojox.geo.openlayers.GeometryFeature(geom); + + constructor : function(/* dojox.geo.openlayers.Geometry */geometry){ + // summary: + // Constructs a GeometryFeature for the specified geometry. + // geometry: OpenLayer.Geometry + // The geometry to render. + this._geometry = geometry; + this._shapeProperties = {}; + this._fill = null; + this._stroke = null; + }, + + _createCollection : function(/* dojox.geo.openlayers.Geometry */g){ + // summary: + // Create collection shape and add it to the viewport. + // tags: + // private + var layer = this.getLayer(); + var s = layer.getSurface(); + var c = this.createShape(s, g); + var vp = layer.getViewport(); + vp.add(c); + return c; + }, + + _getCollectionShape : function(/* dojox.geo.openlayers.Geometry */g){ + // summary: + // Get the collection shape, create it if necessary + // tags: + // private + var s = g.shape; + if (s == null) { + s = this._createCollection(g); + g.shape = s; + } + return s; + }, + + renderCollection : function(/* undefined | dojox.geo.openlayers.Geometry */g){ + // summary: + // Renders a geometry collection. + // g: undefined | dojox.geo.openlayers.Geometry + // The geometry to render. + if (g == undefined) + g = this._geometry; + + s = this._getCollectionShape(g); + var prop = this.getShapeProperties(); + s.setShape(prop); + + array.forEach(g.coordinates, function(item){ + if (item instanceof Point) + this.renderPoint(item); + else if (item instanceof LineString) + this.renderLineString(item); + else if (item instanceof Collection) + this.renderCollection(item); + else + throw new Error(); + }, this); + this._applyStyle(g); + }, + + render : function(/* undefined || dojox.geo.openlayer.Geometry */g){ + // summary: + // Render a geometry. + // Called by the Layer on which the feature is added. + // g: undefined || dojox.geo.openlayer.Geometry + // The geometry to draw + if (g == undefined) + g = this._geometry; + + if (g instanceof Point) + this.renderPoint(g); + else if (g instanceof LineString) + this.renderLineString(g); + else if (g instanceof Collection) + this.renderCollection(g); + else + throw new Error(); + }, + + getShapeProperties : function(){ + // summary: + // Returns the shape properties. + // returns: Object + // The shape properties. + return this._shapeProperties; + }, + + setShapeProperties : function(/* Object */s){ + // summary: + // Sets the shape properties. + // s: Object + // The shape properties to set. + this._shapeProperties = s; + return this; + }, + + createShape : function(/* Surface */s, /* dojox.geo.openlayers.Geometry */g){ + // summary: + // Called when the shape rendering the geometry has to be created. + // This default implementation creates a circle for a point geometry, a polyline for + // a LineString geometry and is recursively called when creating a collection. + // User may replace this method to produce a custom shape. + // s: dojox.gfx.Surface + // The surface on which the method create the shapes. + // g: dojox.geo.openlayers.Geometry + // The reference geometry + // returns: dojox.gfx.Shape + // The resulting shape. + if (!g) + g = this._geometry; + + var shape = null; + if (g instanceof Point) { + shape = s.createCircle(); + } else if (g instanceof LineString) { + shape = s.createPolyline(); + } else if (g instanceof Collection) { + var grp = s.createGroup(); + array.forEach(g.coordinates, function(item){ + var shp = this.createShape(s, item); + grp.add(shp); + }, this); + shape = grp; + } else + throw new Error(); + return shape; + }, + + getShape : function(){ + // summary: + // Retrieves the shape rendering the geometry + // returns: Shape + // The shape used to render the geometry. + var g = this._geometry; + if (!g) + return null; + if (g.shape) + return g.shape; + this.render(); + return g.shape; + }, + + _createPoint : function(/* dojox.geo.openlayer.Geometry */g){ + // summary: + // Create a point shape + // tags: + // private + var layer = this.getLayer(); + var s = layer.getSurface(); + var c = this.createShape(s, g); + var vp = layer.getViewport(); + vp.add(c); + return c; + }, + + _getPointShape : function(/* dojox.geo.openlayers.Geometry */g){ + // summary: + // get the point geometry shape, create it if necessary + // tags: + // private + var s = g.shape; + if (s == null) { + s = this._createPoint(g); + g.shape = s; + } + return s; + }, + + renderPoint : function(/* undefined | dojox.geo.openlayers.Point */g){ + // summary: + // Renders a point geometry. + // g: undefined | dojox.geo.openlayers.Point + // The geometry to render. + if (g == undefined) + g = this._geometry; + var layer = this.getLayer(); + var map = layer.getDojoMap(); + + s = this._getPointShape(g); + var prop = lang.mixin({}, this._defaults.pointShape); + prop = lang.mixin(prop, this.getShapeProperties()); + s.setShape(prop); + + var from = this.getCoordinateSystem(); + var p = map.transform(g.coordinates, from); + + var a = this._getLocalXY(p); + var cx = a[0]; + var cy = a[1]; + var tr = layer.getViewport().getTransform(); + if (tr) + s.setTransform(matrix.translate(cx - tr.dx, cy - tr.dy)); + + this._applyStyle(g); + }, + + _createLineString : function(/* dojox.geo.openlayers.Geometry */g){ + // summary: + // Create polyline shape and add it to the viewport. + // tags: + // private + var layer = this.getLayer(); + var s = layer._surface; + var shape = this.createShape(s, g); + var vp = layer.getViewport(); + vp.add(shape); + g.shape = shape; + return shape; + }, + + _getLineStringShape : function(/* dojox.geo.openlayers.Geometry */g){ + // summary: + // Get the line string geometry shape, create it if necessary + // tags: + // private + var s = g.shape; + if (s == null) { + s = this._createLineString(g); + g.shape = s; + } + return s; + }, + + renderLineString : function(/* undefined | dojox.geo.openlayers.geometry */g){ + // summary: + // Renders a line string geometry. + // g: undefined | dojox.geo.openlayers.Geometry + // The geometry to render. + if (g == undefined) + g = this._geometry; + var layer = this.getLayer(); + var map = layer.getDojoMap(); + var lss = this._getLineStringShape(g); + var from = this.getCoordinateSystem(); + var points = new Array(g.coordinates.length); // ss.getShape().points; + var tr = layer.getViewport().getTransform(); + array.forEach(g.coordinates, function(c, i, array){ + var p = map.transform(c, from); + var a = this._getLocalXY(p); + if (tr) { + a[0] -= tr.dx; + a[1] -= tr.dy; + } + points[i] = { + x : a[0], + y : a[1] + }; + }, this); + var prop = lang.mixin({}, this._defaults.lineStringShape); + prop = lang.mixin(prop, this.getShapeProperties()); + prop = lang.mixin(prop, { + points : points + }); + lss.setShape(prop); + this._applyStyle(g); + }, + + _applyStyle : function(/* Geometry */g){ + // summary: + // Apply the style on the geometry's shape. + // g: dojox.geo.openlayers.Geometry + // The geometry. + // tags: + // private + if (!g || !g.shape) + return; + + var f = this.getFill(); + + var fill; + if (!f || lang.isString(f) || lang.isArray(f)) + fill = f; + else { + fill = lang.mixin({}, this._defaults.fill); + fill = lang.mixin(fill, f); + } + + var s = this.getStroke(); + var stroke; + if (!s || lang.isString(s) || lang.isArray(s)) + stroke = s; + else { + stroke = lang.mixin({}, this._defaults.stroke); + stroke = lang.mixin(stroke, s); + } + + this._applyRecusiveStyle(g, stroke, fill); + }, + + _applyRecusiveStyle : function(g, stroke, fill){ + // summary: + // Apply the style on the geometry's shape recursively. + // g: dojox.geo.openlayers.Geometry + // The geometry. + // stroke: Object + // The stroke + // fill:Object + // The fill + // tags: + // private + var shp = g.shape; + + if (shp.setFill) + shp.setFill(fill); + + if (shp.setStroke) + shp.setStroke(stroke); + + if (g instanceof Collection) { + array.forEach(g.coordinates, function(i){ + this._applyRecusiveStyle(i, stroke, fill); + }, this); + } + }, + + setStroke : function(/* Object */s){ + // summary: + // Set the stroke style to be applied on the rendered shape. + // s: Object + // The stroke style + this._stroke = s; + return this; + }, + + getStroke : function(){ + // summary: + // Retrieves the stroke style + // returns: Object + // The stroke style + return this._stroke; + }, + + setFill : function(/* Object */f){ + // summary: + // Set the fill style to be applied on the rendered shape. + // f: Object + // The fill style + this._fill = f; + return this; + }, + + getFill : function(){ + // summary: + // Retrieves the fill style + // returns: Object + // The fill style + return this._fill; + }, + + remove : function(){ + // summary: + // Removes the shape from the Surface. + // Called when the feature is removed from the layer. + var g = this._geometry; + var shp = g.shape; + g.shape = null; + if (shp) + shp.removeShape(); + if (g instanceof Collection) { + array.forEach(g.coordinates, function(i){ + this.remove(i); + }, this); + } + }, + + _defaults : { + fill : null, + stroke : null, + pointShape : { + r : 30 + }, + lineStringShape : null + } + + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/GfxLayer.js b/js/dojo/dojox/geo/openlayers/GfxLayer.js new file mode 100644 index 0000000..49faa5e --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/GfxLayer.js @@ -0,0 +1,142 @@ +//>>built +define("dojox/geo/openlayers/GfxLayer", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/connect", + "dojo/_base/html", + "dojox/gfx", + "dojox/gfx/_base", + "dojox/gfx/shape", + "dojox/gfx/path", + "dojox/gfx/matrix", + "dojox/geo/openlayers/Feature", + "dojox/geo/openlayers/Layer"], function(dojo, declare, connect, html, gfx, gbase, shape, + path, matrix, Feature, Layer){ + /*===== + var Layer = dojox.geo.openlayers.Layer; + =====*/ + return declare("dojox.geo.openlayers.GfxLayer", Layer, { + // summary: + // A layer dedicated to render dojox.geo.openlayers.GeometryFeature + // description: + // A layer class for rendering geometries as dojox.gfx.Shape objects. + // This layer class accepts Features which encapsulates graphic objects to be added to the map. + // All objects should be added to this group. + // tags: + // private + _viewport : null, + + constructor : function(name, options){ + // summary: + // Constructs a new GFX layer. + var s = dojox.gfx.createSurface(this.olLayer.div, 100, 100); + this._surface = s; + var vp; + if (options && options.viewport) + vp = options.viewport; + else + vp = s.createGroup(); + this.setViewport(vp); + dojo.connect(this.olLayer, "onMapResize", this, "onMapResize"); + this.olLayer.getDataExtent = this.getDataExtent; + }, + + getViewport : function(){ + // summary: + // Gets the viewport + // tags: + // internal + return this._viewport; + }, + + setViewport : function(g){ + // summary: + // Sets the viewport + // g: dojox.gfx.Group + // tags: + // internal + if (this._viewport) + this._viewport.removeShape(); + this._viewport = g; + this._surface.add(g); + }, + + onMapResize : function(){ + // summary: + // Called when map is resized. + // tag: + // protected + this._surfaceSize(); + }, + + setMap : function(map){ + // summary: + // Sets the map for this layer. + // tag: + // protected + this.inherited(arguments); + this._surfaceSize(); + }, + + getDataExtent : function(){ + // summary: + // Get data extent + // tags: + // private + var ret = this._surface.getDimensions(); + return ret; + }, + + getSurface : function(){ + // summary: + // Get the underlying dojox.gfx.Surface + // returns: dojox.gfx.Surface + // The dojox.gfx.Surface this layer uses to draw its GFX rendering. + return this._surface; + }, + + _surfaceSize : function(){ + // summary: + // Recomputes the surface size when being resized. + // tags: + // private + var s = this.olLayer.map.getSize(); + this._surface.setDimensions(s.w, s.h); + }, + + moveTo : function(event){ + // summary: + // Called when this layer is moved or zoommed. + // event: + // The event + var s = dojo.style(this.olLayer.map.layerContainerDiv); + var left = parseInt(s.left); + var top = parseInt(s.top); + + if (event.zoomChanged || left || top) { + var d = this.olLayer.div; + + dojo.style(d, { + left : -left + "px", + top : -top + "px" + }); + + if (this._features == null) + return; + var vp = this.getViewport(); + + vp.setTransform(matrix.translate(left, top)); + + this.inherited(arguments); + + } + }, + + added : function(){ + // summary: + // Called when added to a map. + this.inherited(arguments); + this._surfaceSize(); + } + + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/GreatCircle.js b/js/dojo/dojox/geo/openlayers/GreatCircle.js new file mode 100644 index 0000000..ae0b67d --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/GreatCircle.js @@ -0,0 +1,122 @@ +//>>built +define("dojox/geo/openlayers/GreatCircle", ["dojo/_base/lang", + "dojox/geo/openlayers/GeometryFeature", + "dojox/geo/openlayers/Point", + "dojox/geo/openlayers/LineString"], function(lang, GeometryFeature, Point, lineString){ + + lang.getObject("geo.openlayers", true, dojox); + + dojox.geo.openlayers.GreatCircle = { + + toPointArray : function(p1, p2, increment){ + // summary: + // Create a geodetic line as an array of OpenLayers.Point. + // descritpion: + // Create a geodetic line as an array of OpenLayers.Point between the point p1 + // and the point p2. Result is a polyline approximation for which a new point is + // calculated every <em>increment</em> degrees. + // p1: Point + // The first point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // p2: Point + // The second point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // increment: Float + // The value at which a new point is computed. + var startLon = p1.x; + var endLon = p2.x; + var sl = Math.min(startLon, endLon); + var el = Math.max(startLon, endLon); + + var d2r = this.DEG2RAD; + var lat1 = p1.y * d2r; + var lon1 = p1.x * d2r; + var lat2 = p2.y * d2r; + var lon2 = p2.x * d2r; + + if (Math.abs(lon1 - lon2) <= this.TOLERANCE) { + var l = Math.min(lon1, lon2); + lon2 = l + Math.PI; + } + + if (Math.abs(lon2 - lon1) == Math.PI) { + if (lat1 + lat2 == 0.0) { + lat2 += Math.PI / 180000000; + } + } + + var lon = sl * d2r; + var elon = el * d2r; + var incr = increment * d2r; + var wp = []; + var k = 0; + var r2d = this.RAD2DEG; + + while (lon <= elon) { + lat = Math.atan((Math.sin(lat1) * Math.cos(lat2) * Math.sin(lon - lon2) - Math.sin(lat2) * Math.cos(lat1) + * Math.sin(lon - lon1)) + / (Math.cos(lat1) * Math.cos(lat2) * Math.sin(lon1 - lon2))); + var p = { + x : lon * r2d, + y : lat * r2d + }; + wp[k++] = p; + if (lon < elon && (lon + incr) >= elon) + lon = elon; + else + lon = lon + incr; + } + return wp; + }, + + toLineString : function(p1, p2, increment){ + // summary: + // Create a geodetic line as an array of OpenLayers.Geometry.LineString. + // descritpion: + // Create a geodetic line as a OpenLayers.Geometry.LineString between the point p1 + // and the point p2. Result is a polyline approximation for which a new point is + // calculated every <em>increment</em> degrees. + // p1: Point + // The first point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // p2: Point + // The second point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // increment: Float + // The value at which a new point is computed. + var wp = this.toPointArray(p1, p2, increment); + var ls = new OpenLayers.Geometry.LineString(wp); + return ls; + }, + + toGeometryFeature : function(p1, p2, increment){ + // summary: + // Create a geodetic line as an array of dojox.geo.openlayers.GeometryFeature. + // description: + // Create a geodetic line as a dojox.geo.openlayers.GeometryFeature between the point p1 + // ant the point p2. Result is a polyline approximation for which a new point is + // calculated every <em>increment</em> degrees. + // p1: Point + // The first point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // p2: Point + // The second point of the geodetic line. x and y fields are longitude and + // latitude in decimal degrees. + // increment: Float + // The value at which a new point is computed. + // returns: GeometryFeature + // The geodetic line as a GeometryFeature + + var ls = this.toLineString(p1, p2, increment); + return new GeometryFeature(ls); + }, + + DEG2RAD : Math.PI / 180, + + RAD2DEG : 180 / Math.PI, + + TOLERANCE : 0.00001 + }; + + return dojox.geo.openlayers.GreatCircle; +}); diff --git a/js/dojo/dojox/geo/openlayers/JsonImport.js b/js/dojo/dojox/geo/openlayers/JsonImport.js new file mode 100644 index 0000000..ebb9197 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/JsonImport.js @@ -0,0 +1,151 @@ +//>>built +define("dojox/geo/openlayers/JsonImport", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/xhr", + "dojo/_base/lang", + "dojo/_base/array", + "dojox/geo/openlayers/LineString", + "dojox/geo/openlayers/Collection", + "dojo/data/ItemFileReadStore", + "dojox/geo/openlayers/GeometryFeature"], function(dojo, declare, xhr, lang, array, LineString, Collection, + ItemFileReadStore, GeometryFeature){ + + return declare("dojox.geo.openlayers.JsonImport", null, { + // summary: + // Class to load JSON formated ShapeFile as output of the JSon Custom Map Converter. + // description: + // This class loads JSON formated ShapeFile produced by the JSon Custom Map Converter. + // When loading the JSON file, it calls a iterator function each time a feature is read. + // This iterator function is provided as parameter to the constructor. + // + constructor : function(/* Object */params){ + // summary: + // Construct a new JSON importer. + // description: + // Construct a new JSON importer with the specified parameters. These parameters are + // passed through an Object and include: + // <ul> + // <li> url : <em>url</em> </li> The url pointing to the JSON file to load. + // <li> nextFeature : <em>function</em> </li> The function called each time a feature is read. + // The function is called with a GeometryFeature as argument. + // <li> error : <em>function</em> </li> Error function called if something goes wrong. + // </ul> + this._params = params; + }, + + loadData : function(){ + // summary: + // Triggers the loading. + var p = this._params; + xhr.get({ + url : p.url, + handleAs : "json", + sync : true, + load : lang.hitch(this, this._gotData), + error : lang.hitch(this, this._loadError) + }); + }, + + _gotData : function(/* Object */items){ + // summary: + // Called when loading is complete. + // tags: + // private + var nf = this._params.nextFeature; + if (!lang.isFunction(nf)) + return; + + var extent = items.layerExtent; + var ulx = extent[0]; + var uly = extent[1]; + var lrx = ulx + extent[2]; + var lry = uly + extent[3]; + + var extentLL = items.layerExtentLL; + var x1 = extentLL[0]; + var y1 = extentLL[1]; + var x2 = x1 + extentLL[2]; + var y2 = y1 + extentLL[3]; + + var ulxLL = x1; + var ulyLL = y2; + var lrxLL = x2; + var lryLL = y1; + + var features = items.features; + + for ( var f in features) { + var o = features[f]; + var s = o["shape"]; + var gf = null; + if (lang.isArray(s[0])) { + + var a = new Array(); + array.forEach(s, function(item){ + var ls = this._makeGeometry(item, ulx, uly, lrx, lry, ulxLL, ulyLL, lrxLL, lryLL); + a.push(ls); + }, this); + var g = new Collection(a); + gf = new GeometryFeature(g); + nf.call(this, gf); + + } else { + gf = this._makeFeature(s, ulx, uly, lrx, lry, ulxLL, ulyLL, lrxLL, lryLL); + nf.call(this, gf); + } + } + var complete = this._params.complete; + if (lang.isFunction(complete)) + complete.call(this, complete); + }, + + _makeGeometry : function(/* Array */s, /* Float */ulx, /* Float */uly, /* Float */lrx, /* Float */ + lry, /* Float */ulxLL, /* Float */ulyLL, /* Float */lrxLL, /* Float */lryLL){ + // summary: + // Make a geometry with the specified points. + // tags: + // private + var a = []; + var k = 0.0; + for ( var i = 0; i < s.length - 1; i += 2) { + var x = s[i]; + var y = s[i + 1]; + + k = (x - ulx) / (lrx - ulx); + var px = k * (lrxLL - ulxLL) + ulxLL; + + k = (y - uly) / (lry - uly); + var py = k * (lryLL - ulyLL) + ulyLL; + + a.push({ + x : px, + y : py + }); + + } + var ls = new LineString(a); + return ls; + }, + + _makeFeature : function(/* Array */s, /* Float */ulx, /* Float */uly, /* Float */lrx, /* Float */ + lry, /* Float */ulxLL, /* Float */ulyLL, /* Float */lrxLL, /* Float */lryLL){ + // summary: + // Make a GeometryFeature with the specified points. + // tags: + // private + var ls = this._makeGeometry(s, ulx, uly, lrx, lry, ulxLL, ulyLL, lrxLL, lryLL); + var gf = new GeometryFeature(ls); + return gf; + }, + + _loadError : function(){ + // summary: + // Called when an error occurs. Calls the error function is provided in the parameters. + // tags: + // private + var f = this._params.error; + if (lang.isFunction(f)) + f.apply(this, parameters); + } + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/Layer.js b/js/dojo/dojox/geo/openlayers/Layer.js new file mode 100644 index 0000000..c01f79f --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Layer.js @@ -0,0 +1,164 @@ +//>>built +define("dojox/geo/openlayers/Layer", ["dojo/_base/kernel", "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/sniff"], + function(dojo, declare, lang, array, sniff){ + + return declare("dojox.geo.openlayers.Layer", null, { + // summary: + // Base layer class for dojox.geo.openlayers.Map specific layers extending OpenLayers.Layer class. + // This layer class accepts Features which encapsulates graphic objects to be added to the map. + // This layer class encapsulates an OpenLayers.Layer. + // This class provides Feature management such as add, remove and feature access. + constructor : function(name, options){ + // summary: + // Constructs a new Layer. + // name: String + // The name of the layer. + // options: Object + // Options passed to the underlying OpenLayers.Layer object. + + var ol = options ? options.olLayer : null; + + if (!ol) + ol = lang.delegate(new OpenLayers.Layer(name, options)); + + this.olLayer = ol; + this._features = null; + this.olLayer.events.register("moveend", this, lang.hitch(this, this.moveTo)); + }, + + renderFeature : function(/* Feature */f){ + // summary: + // Called when rendering a feature is necessary. + // f : Feature + // The feature to draw. + f.render(); + }, + + getDojoMap : function(){ + return this.dojoMap; + }, + + addFeature : function(/* Feature | Array */f){ + // summary: + // Add a feature or an array of features to the layer. + // f : Feature or Array + // The Feature or array of features to add. + if (lang.isArray(f)) { + array.forEach(f, function(item){ + this.addFeature(item); + }, this); + return; + } + if (this._features == null) + this._features = []; + this._features.push(f); + f._setLayer(this); + }, + + removeFeature : function(/* Feature | Array */f){ + // summary : + // Removes a feature or an array of features from the layer. + // f : Feature or Array + // The Feature or array of features to remove. + var ft = this._features; + if (ft == null) + return; + if (f instanceof Array) { + f = f.slice(0); + array.forEach(f, function(item){ + this.removeFeature(item); + }, this); + return; + } + var i = array.indexOf(ft, f); + if (i != -1) + ft.splice(i, 1); + f._setLayer(null); + f.remove(); + }, + + removeFeatureAt : function(index){ + // summary: + // Remove the feature at the specified index. + // description: + // Remove the feature at the specified index. + // index: Number + // The index of the feature to remove. + var ft = this._features; + var f = ft[index]; + if (!f) + return; + ft.splice(index, 1); + f._setLayer(null); + f.remove(); + }, + + getFeatures : function(){ + // summary: + // Retrieves the feature hold by this layer. + // returns: Array + // The untouched array of features hold by this layer. + return this._features; + }, + + getFeatureAt : function(i){ + // summary: + // Returns the i-th feature of this layer. + // i : int + // The index of the feature to return. + // returns : ibm_maps.maps.Layer + // The i-th feature of this layer. + if (this._features == null) + return undefined; + return this._features[i]; + }, + + getFeatureCount : function(){ + // summary: + // Returns the number of the features contained by this layer. + // returns: int + // The number of the features contained by this layer. + if (this._features == null) + return 0; + return this._features.length; + }, + + clear : function(){ + // summary: + // Removes all the features from this layer. + var fa = this.getFeatures(); + this.removeFeature(fa); + }, + + moveTo : function(event){ + // summary: + // Called when the layer is panned or zoomed. + // event: Object + // The event + if (event.zoomChanged) { + if (this._features == null) + return; + array.forEach(this._features, function(f){ + this.renderFeature(f); + }, this); + } + }, + + redraw : function(){ + // summary: + // Redraws this layer + if (sniff.isIE) + setTimeout(lang.hitch(this, function(){ + this.olLayer.redraw(); + }, 0)); + else + this.olLayer.redraw(); + }, + + added : function(){ + // summary: + // Called when the layer is added to the map + } + + }); + }); diff --git a/js/dojo/dojox/geo/openlayers/LineString.js b/js/dojo/dojox/geo/openlayers/LineString.js new file mode 100644 index 0000000..d5d1c0e --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/LineString.js @@ -0,0 +1,28 @@ +//>>built +define("dojox/geo/openlayers/LineString", ["dojo/_base/kernel", "dojo/_base/declare", "dojox/geo/openlayers/Geometry"], function(dojo, declare, + /* ===== + var Geometry = dojox.geo.openlayers.Geometry; + =====*/ Geometry){ + return declare("dojox.geo.openlayers.LineString", Geometry, { + // summary: + // The `dojox.geo.openlayers.LineString` geometry. This geometry holds an array + // of coordinates. + + setPoints : function(p){ + // summary: + // Sets the points for this geometry. + // p : Array + // An array of {x, y} objects + this.coordinates = p; + }, + + getPoints : function(){ + // summary: + // Gets the points of this geometry. + // returns: Array + // The points of this geometry. + return this.coordinates; + } + + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/Map.js b/js/dojo/dojox/geo/openlayers/Map.js new file mode 100644 index 0000000..890f736 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Map.js @@ -0,0 +1,592 @@ +//>>built +define("dojox/geo/openlayers/Map", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/_base/array", + "dojo/_base/json", + "dojo/_base/html", + "dojox/main", + "dojox/geo/openlayers/TouchInteractionSupport", + "dojox/geo/openlayers/Layer", + "dojox/geo/openlayers/Patch"], function(dojo, declare, lang, array, json, html, dojox, TouchInteractionSupport, + Layer, Patch){ + + dojo.experimental("dojox.geo.openlayers.Map"); + + lang.getObject("geo.openlayers", true, dojox); + + dojox.geo.openlayers.BaseLayerType = { + // summary: + // Defines the base layer types to be used at Map construction time or + // with the setBaseLayerType function. + // description: + // This object defines the base layer types to be used at Map construction + // time or with the setBaseLayerType function. + // OSM: String + // The Open Street Map base layer type selector. + OSM : "OSM", + // WMS: String + // The Web Map Server base layer type selector. + WMS : "WMS", + // GOOGLE: String + // The Google base layer type selector. + GOOGLE : "Google", + // VIRTUAL_EARTH: String + // The Virtual Earth base layer type selector. + VIRTUAL_EARTH : "VirtualEarth", + // BING: String + // Same as Virtual Earth + BING : "VirtualEarth", + // YAHOO: String + // The Yahoo base layer type selector. + YAHOO : "Yahoo", + // ARCGIS: String + // The ESRI ARCGis base layer selector. + ARCGIS : "ArcGIS" + }; + + dojox.geo.openlayers.EPSG4326 = new OpenLayers.Projection("EPSG:4326"); + + var re = /^\s*(\d{1,3})[D°]\s*(\d{1,2})[M']\s*(\d{1,2}\.?\d*)\s*(S|"|'')\s*([NSEWnsew]{0,1})\s*$/i; + dojox.geo.openlayers.parseDMS = function(v, toDecimal){ + // summary: + // Parses the specified string and returns degree minute second or decimal degree. + // description: + // Parses the specified string and returns degree minute second or decimal degree. + // v: String + // The string to parse + // toDecimal: Boolean + // Specifies if the result should be returned in decimal degrees or in an array + // containg the degrees, minutes, seconds values. + // returns: Float | Array + // the parsed value in decimal degrees or an array containing the degrees, minutes, seconds values. + + var res = re.exec(v); + if (res == null || res.length < 5) + return parseFloat(v); + var d = parseFloat(res[1]); + var m = parseFloat(res[2]); + var s = parseFloat(res[3]); + var nsew = res[5]; + if (toDecimal) { + var lc = nsew.toLowerCase(); + var dd = d + (m + s / 60.0) / 60.0; + if (lc == "w" || lc == "s") + dd = -dd; + return dd; + } + return [d, m, s, nsew]; + }; + + Patch.patchGFX(); + + return declare("dojox.geo.openlayers.Map", null, { + // summary: + // A map viewer based on the OpenLayers library. + // + // description: + // The `dojox.geo.openlayers.Map` object allows to view maps from various map providers. + // It encapsulates an `OpenLayers.Map` object on which most operations are delegated. + // GFX layers can be added to display GFX georeferenced shapes as well as Dojo widgets. + // Parameters can be passed as argument at construction time to define the base layer + // type and the base layer parameters such as url or options depending on the type + // specified. These parameters can be any of : + // <br /> + // _baseLayerType_: type of the base layer. Can be any of + // + // * `dojox.geo.openlayers.BaseLayerType.OSM`: Open Street Map base layer + // * `dojox.geo.openlayers.BaseLayerType.WMS`: Web Map Service layer + // * `dojox.geo.openlayers.BaseLayerType.GOOGLE`: Google layer + // * `dojox.geo.openlayers.BaseLayerType.VIRTUAL_EARTH`: Virtual Earth layer + // * `dojox.geo.openlayers.BaseLayerType.BING`: Bing layer + // * `dojox.geo.openlayers.BaseLayerType.YAHOO`: Yahoo layer + // * `dojox.geo.openlayers.BaseLayerType.ARCGIS`: ESRI ArgGIS layer + // + // Note that access to commercial server such as Google, Virtual Earth or Yahoo may need specific licencing. + // + // The parameters value also include : + // + // * `baseLayerName`: The name of the base layer. + // * `baseLayerUrl`: Some layer may need an url such as Web Map Server + // * `baseLayerOptions`: Addtional specific options passed to OpensLayers layer, + // such as The list of layer to display, for Web Map Server layer. + // + // example: + // + // | var map = new dojox.geo.openlayers.widget.Map(div, { + // | baseLayerType : dojox.geo.openlayers.BaseLayerType.OSM, + // | baseLayerName : 'Open Street Map Layer' + // | }); + + // summary: + // The underlying OpenLayers.Map object. + // Should be accessed on read mode only. + olMap : null, + + _tp : null, + + constructor : function(div, options){ + // summary: + // Constructs a new Map object + if (!options) + options = {}; + + div = html.byId(div); + + this._tp = { + x : 0, + y : 0 + }; + + var opts = options.openLayersMapOptions; + + if (!opts) { + opts = { + controls : [new OpenLayers.Control.ScaleLine({ + maxWidth : 200 + }), new OpenLayers.Control.Navigation()] + }; + } + if (options.accessible) { + var kbd = new OpenLayers.Control.KeyboardDefaults(); + if (!opts.controls) + opts.controls = []; + opts.controls.push(kbd); + } + var baseLayerType = options.baseLayerType; + if (!baseLayerType) + baseLayerType = dojox.geo.openlayers.BaseLayerType.OSM; + + html.style(div, { + width : "100%", + height : "100%", + dir : "ltr" + }); + + var map = new OpenLayers.Map(div, opts); + this.olMap = map; + + this._layerDictionary = { + olLayers : [], + layers : [] + }; + + if (options.touchHandler) + this._touchControl = new TouchInteractionSupport(map); + + var base = this._createBaseLayer(options); + this.addLayer(base); + + this.initialFit(options); + }, + + initialFit : function(params){ + var o = params.initialLocation; + if (!o) + o = [-160, 70, 160, -70]; + this.fitTo(o); + }, + + setBaseLayerType : function( + /* dojox.geo.openlayers.Map.BaseLayerType */type){ + // summary: + // Set the base layer type, replacing the existing base layer + // type: dojox.geo.openlayers.BaseLayerType + // base layer type + // returns: OpenLayers.Layer + // The newly created layer. + if (type == this.baseLayerType) + return null; + + var o = null; + if (typeof type == "string") { + o = { + baseLayerName : type, + baseLayerType : type + }; + this.baseLayerType = type; + } else if (typeof type == "object") { + o = type; + this.baseLayerType = o.baseLayerType; + } + var bl = null; + if (o != null) { + bl = this._createBaseLayer(o); + if (bl != null) { + var olm = this.olMap; + var ob = olm.getZoom(); + var oc = olm.getCenter(); + var recenter = !!oc && !!olm.baseLayer && !!olm.baseLayer.map; + + if (recenter) { + var proj = olm.getProjectionObject(); + if (proj != null) + oc = oc.transform(proj, dojox.geo.openlayers.EPSG4326); + } + var old = olm.baseLayer; + if (old != null) { + var l = this._getLayer(old); + this.removeLayer(l); + } + if (bl != null) + this.addLayer(bl); + if (recenter) { + proj = olm.getProjectionObject(); + if (proj != null) + oc = oc.transform(dojox.geo.openlayers.EPSG4326, proj); + olm.setCenter(oc, ob); + } + } + } + return bl; + }, + + getBaseLayerType : function(){ + // summary: + // Retrieves the base layer type. + // returns: dojox.geo.openlayers.BaseLayerType + // The current base layer type. + return this.baseLayerType; + }, + + getScale : function(geodesic){ + // summary: + // Returns the current scale + // geodesic: Boolean + // Tell if geodesic calculation should be performed. If set to + // true, the scale will be calculated based on the horizontal size of the + // pixel in the center of the map viewport. + // returns: Number + // The current scale. + var scale; + var om = this.olMap; + if (geodesic) { + var units = om.getUnits(); + if (!units) { + return null; + } + var inches = OpenLayers.INCHES_PER_UNIT; + scale = (om.getGeodesicPixelSize().w || 0.000001) * inches["km"] * OpenLayers.DOTS_PER_INCH; + } else { + scale = om.getScale(); + } + return scale; + }, + + getOLMap : function(){ + // summary: + // gets the underlying OpenLayers map object. + // returns : OpenLayers.Map + // The underlying OpenLayers map object. + return this.olMap; + }, + + _createBaseLayer : function(params){ + // summary: + // Creates the base layer. + // tags: + // private + var base = null; + var type = params.baseLayerType; + var url = params.baseLayerUrl; + var name = params.baseLayerName; + var options = params.baseLayerOptions; + + if (!name) + name = type; + if (!options) + options = {}; + switch (type) { + case dojox.geo.openlayers.BaseLayerType.OSM: + options.transitionEffect = "resize"; + // base = new OpenLayers.Layer.OSM(name, url, options); + base = new Layer(name, { + olLayer : new OpenLayers.Layer.OSM(name, url, options) + }); + break; + case dojox.geo.openlayers.BaseLayerType.WMS: + if (!url) { + url = "http://labs.metacarta.com/wms/vmap0"; + if (!options.layers) + options.layers = "basic"; + } + base = new Layer(name, { + olLayer : new OpenLayers.Layer.WMS(name, url, options, { + transitionEffect : "resize" + }) + }); + break; + case dojox.geo.openlayers.BaseLayerType.GOOGLE: + base = new Layer(name, { + olLayer : new OpenLayers.Layer.Google(name, options) + }); + break; + case dojox.geo.openlayers.BaseLayerType.VIRTUAL_EARTH: + base = new Layer(name, { + olLayer : new OpenLayers.Layer.VirtualEarth(name, options) + }); + break; + case dojox.geo.openlayers.BaseLayerType.YAHOO: + // base = new OpenLayers.Layer.Yahoo(name); + base = new Layer(name, { + olLayer : new OpenLayers.Layer.Yahoo(name, options) + }); + break; + case dojox.geo.openlayers.BaseLayerType.ARCGIS: + if (!url) + url = "http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer/export"; + base = new Layer(name, { + olLayer : new OpenLayers.Layer.ArcGIS93Rest(name, url, options, {}) + }); + + break; + } + + if (base == null) { + if (type instanceof OpenLayers.Layer) + base = type; + else { + options.transitionEffect = "resize"; + base = new Layer(name, { + olLayer : new OpenLayers.Layer.OSM(name, url, options) + }); + this.baseLayerType = dojox.geo.openlayers.BaseLayerType.OSM; + } + } + + return base; + }, + + removeLayer : function(/* dojox.geo.openlayers.Layer */layer){ + // summary: + // Remove the specified layer from the map. + // layer: dojox.geo.openlayers.Layer + // The layer to remove from the map. + var om = this.olMap; + var i = array.indexOf(this._layerDictionary.layers, layer); + if (i > 0) + this._layerDictionary.layers.splice(i, 1); + var oll = layer.olLayer; + var j = array.indexOf(this._layerDictionary.olLayers, oll); + if (j > 0) + this._layerDictionary.olLayers.splice(i, j); + om.removeLayer(oll, false); + }, + + layerIndex : function(/* dojox.geo.openlayers.Layer */layer, index){ + // summary: + // Set or retrieve the layer index. + // description: + // Set or get the layer index, that is the z-order of the layer. + // if the index parameter is provided, the layer index is set to + // this value. If the index parameter is not provided, the index of + // the layer is returned. + // index: undefined | int + // index of the layer + // returns: int + // the index of the layer. + var olm = this.olMap; + if (!index) + return olm.getLayerIndex(layer.olLayer); + //olm.raiseLayer(layer.olLayer, index); + olm.setLayerIndex(layer.olLayer, index); + + this._layerDictionary.layers.sort(function(l1, l2){ + return olm.getLayerIndex(l1.olLayer) - olm.getLayerIndex(l2.olLayer); + }); + this._layerDictionary.olLayers.sort(function(l1, l2){ + return olm.getLayerIndex(l1) - olm.getLayerIndex(l2); + }); + + return index; + }, + + addLayer : function(/* dojox.geo.openlayers.Layer */layer){ + // summary: + // Add the specified layer to the map. + // layer: dojox.geo.openlayer.Layer + // The layer to add to the map. + layer.dojoMap = this; + var om = this.olMap; + var ol = layer.olLayer; + this._layerDictionary.olLayers.push(ol); + this._layerDictionary.layers.push(layer); + om.addLayer(ol); + layer.added(); + }, + + _getLayer : function(/*OpenLayer.Layer */ol){ + // summary: + // Retrieve the dojox.geo.openlayer.Layer from the OpenLayer.Layer + // tags: + // private + var i = array.indexOf(this._layerDictionary.olLayers, ol); + if (i != -1) + return this._layerDictionary.layers[i]; + return null; + }, + + getLayer : function(property, value){ + // summary: + // Returns the layer whose property matches the value. + // property: String + // The property to check + // value: Object + // The value to match + // returns: dojox.geo.openlayer.Layer | Array + // The layer(s) matching the property's value. Since multiple layers + // match the property's value the return value is an array. + // example: + // var layers = map.getLayer("name", "Layer Name"); + var om = this.olMap; + var ols = om.getBy("layers", property, value); + var ret = new Array(); //[]; + array.forEach(ols, function(ol){ + ret.push(this._getLayer(ol)); + }, this); + return ret; + }, + + getLayerCount : function(){ + // summary: + // Returns the count of layers of this map. + // returns: int + // The number of layers of this map. + var om = this.olMap; + if (om.layers == null) + return 0; + return om.layers.length; + }, + + fitTo : function(o){ + // summary: + // Fits the map on a point,or an area + // description: + // Fits the map on the point or extent specified as parameter. + // o: Object + // Object with key values fit parameters or a JSON string. + // example: + // Examples of arguments passed to the fitTo function : + // | null + // The map is fit on full extent + // + // | { + // | bounds : [ulx, uly, lrx, lry] + // | } + // The map is fit on the specified bounds expressed as decimal degrees latitude and longitude. + // The bounds are defined with their upper left and lower right corners coordinates. + // + // | { + // | position : [longitude, latitude], + // | extent : degrees + // | } + // The map is fit on the specified position showing the extent <extent> around + // the specified center position. + + var map = this.olMap; + var from = dojox.geo.openlayers.EPSG4326; + + if (o == null) { + var c = this.transformXY(0, 0, from); + map.setCenter(new OpenLayers.LonLat(c.x, c.y)); + return; + } + var b = null; + if (typeof o == "string") + var j = json.fromJson(o); + else + j = o; + var ul; + var lr; + if (j.hasOwnProperty("bounds")) { + var a = j.bounds; + b = new OpenLayers.Bounds(); + ul = this.transformXY(a[0], a[1], from); + b.left = ul.x; + b.top = ul.y; + lr = this.transformXY(a[2], a[3], from); + b.right = lr.x; + b.bottom = lr.y; + } + if (b == null) { + if (j.hasOwnProperty("position")) { + var p = j.position; + var e = j.hasOwnProperty("extent") ? j.extent : 1; + if (typeof e == "string") + e = parseFloat(e); + b = new OpenLayers.Bounds(); + ul = this.transformXY(p[0] - e, p[1] + e, from); + b.left = ul.x; + b.top = ul.y; + lr = this.transformXY(p[0] + e, p[1] - e, from); + b.right = lr.x; + b.bottom = lr.y; + } + } + if (b == null) { + if (o.length == 4) { + b = new OpenLayers.Bounds(); + // TODO Choose the correct method + if (false) { + b.left = o[0]; + b.top = o[1]; + + b.right = o[2]; + b.bottom = o[3]; + } else { + ul = this.transformXY(o[0], o[1], from); + b.left = ul.x; + b.top = ul.y; + lr = this.transformXY(o[2], o[3], from); + b.right = lr.x; + b.bottom = lr.y; + } + } + } + if (b != null) { + map.zoomToExtent(b, true); + } + }, + + transform : function(p, from, to){ + // summary: + // Transforms the point passed as argument, expressed in the <em>from</em> + // coordinate system to the map coordinate system. + // description: + // Transforms the point passed as argument without modifying it. The point is supposed to be expressed + // in the <em>from</em> coordinate system and is transformed to the map coordinate system. + // p : Object {x, y} + // The point to transform + // from: OpenLayers.Projection + // The projection in which the point is expressed. + return this.transformXY(p.x, p.y, from, to); + }, + + transformXY : function(x, y, from, to){ + // summary + // Transforms the coordinates passed as argument, expressed in the <em>from</em> + // coordinate system to the map coordinate system. + // description: + // Transforms the coordinates passed as argument. The coordinate are supposed to be expressed + // in the <em>from</em> coordinate system and are transformed to the map coordinate system. + // x : Number + // The longitude coordinate to transform. + // y : Number + // The latitude coordinate to transform. + // from: OpenLayers.Projection + // The projection in which the point is expressed. + + var tp = this._tp; + tp.x = x; + tp.y = y; + if (!from) + from = dojox.geo.openlayers.EPSG4326; + if (!to) + to = this.olMap.getProjectionObject(); + tp = OpenLayers.Projection.transform(tp, from, to); + return tp; + } + + }); + +}); diff --git a/js/dojo/dojox/geo/openlayers/Patch.js b/js/dojo/dojox/geo/openlayers/Patch.js new file mode 100644 index 0000000..93dbe56 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Patch.js @@ -0,0 +1,67 @@ +//>>built +define("dojox/geo/openlayers/Patch", [ + "dojo/_base/kernel", + "dojo/_base/lang", // dojo.extend getObject + "dojo/_base/sniff", // dojo.isIE + "dojox/gfx", + "dojox/gfx/shape" +], function(dojo, lang, sniff, gfx, shape){ + + var dgo = lang.getObject("geo.openlayers", true, dojox); + + dgo.Patch = { + + patchMethod : function(/*Object*/type, /*String*/method, /*Function*/execBefore, /*Function*/ + execAfter){ + // summary: + // Patches the specified method of the given type so that the 'execBefore' (resp. 'execAfter') function is + // called before (resp. after) invoking the legacy implementation. + // description: + // The execBefore function is invoked with the following parameter: + // execBefore(method, arguments) where 'method' is the patched method name and 'arguments' the arguments received + // by the legacy implementation. + // The execAfter function is invoked with the following parameter: + // execBefore(method, returnValue, arguments) where 'method' is the patched method name, 'returnValue' the value + // returned by the legacy implementation and 'arguments' the arguments received by the legacy implementation. + // type: Object: the type to patch. + // method: String: the method name. + // execBefore: Function: the function to execute before the legacy implementation. + // execAfter: Function: the function to execute after the legacy implementation. + // tags: + // private + var old = type.prototype[method]; + type.prototype[method] = function(){ + var callee = method; + if (execBefore) + execBefore.call(this, callee, arguments); + var ret = old.apply(this, arguments); + if (execAfter) + ret = execAfter.call(this, callee, ret, arguments) || ret; + return ret; + }; + }, + + patchGFX : function(){ + + var vmlFixRawNodePath = function(){ + if (!this.rawNode.path) + this.rawNode.path = {}; + }; + + var vmlFixFillColors = function() { + if(this.rawNode.fill && !this.rawNode.fill.colors) + this.rawNode.fill.colors = {}; + }; + + if (sniff.isIE <= 8) { + + dojox.geo.openlayers.Patch.patchMethod(gfx.Line, "setShape", vmlFixRawNodePath, null); + dojox.geo.openlayers.Patch.patchMethod(gfx.Polyline, "setShape", vmlFixRawNodePath, null); + dojox.geo.openlayers.Patch.patchMethod(gfx.Path, "setShape", vmlFixRawNodePath, null); + + dojox.geo.openlayers.Patch.patchMethod(shape.Shape, "setFill", vmlFixFillColors, null); + } + } + }; + return dgo.Patch; +}); diff --git a/js/dojo/dojox/geo/openlayers/Point.js b/js/dojo/dojox/geo/openlayers/Point.js new file mode 100644 index 0000000..51db5a1 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/Point.js @@ -0,0 +1,27 @@ +//>>built +define("dojox/geo/openlayers/Point", ["dojo/_base/kernel", "dojo/_base/declare", "dojox/geo/openlayers/Geometry"], + function(dojo, declare, Geometry){ + /*===== + var Geometry = dojox.geo.openlayers.Geometry; + =====*/ + return declare("dojox.geo.openlayers.Point", Geometry, { + // summary: + // A Point geometry handles description of points to be rendered in a GfxLayer + + setPoint : function(p){ + // summary: + // Sets the point for this geometry. + // p : {x, y} Object + // The point geometry. + this.coordinates = p; + }, + + getPoint : function(){ + // summary: + // Gets the point defining this geometry. + // returns: {x, y} Object + // The point defining this geometry. + return this.coordinates; + } + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/TouchInteractionSupport.js b/js/dojo/dojox/geo/openlayers/TouchInteractionSupport.js new file mode 100644 index 0000000..0ea58f6 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/TouchInteractionSupport.js @@ -0,0 +1,248 @@ +//>>built +define("dojox/geo/openlayers/TouchInteractionSupport", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/connect", + "dojo/_base/html", + "dojo/_base/lang", + "dojo/_base/event", + "dojo/_base/window"], function(dojo, declare, connect, html, lang, event, window){ + + return declare("dojox.geo.openlayers.TouchInteractionSupport", null, { + // summary: + // class to handle touch interactions on a OpenLayers.Map widget + // tags: + // private + + _map : null, + _centerTouchLocation : null, + _touchMoveListener : null, + _touchEndListener : null, + _initialFingerSpacing : null, + _initialScale : null, + _tapCount : null, + _tapThreshold : null, + _lastTap : null, + + constructor : function(/* OpenLayers.Map */map){ + // summary: + // Constructs a new TouchInteractionSupport instance + // map: OpenLayers.Map + // the Map widget this class provides touch navigation for. + this._map = map; + this._centerTouchLocation = new OpenLayers.LonLat(0, 0); + + var div = this._map.div; + + // install touch listeners + connect.connect(div, "touchstart", this, this._touchStartHandler); + connect.connect(div, "touchmove", this, this._touchMoveHandler); + connect.connect(div, "touchend", this, this._touchEndHandler); + + this._tapCount = 0; + this._lastTap = { + x : 0, + y : 0 + }; + this._tapThreshold = 100; // square distance in pixels + + }, + + _getTouchBarycenter : function(touchEvent){ + // summary: + // returns the midpoint of the two first fingers (or the first finger location if only one) + // touchEvent: Event + // a touch event + // returns: dojox.gfx.Point + // the midpoint + // tags: + // private + var touches = touchEvent.touches; + var firstTouch = touches[0]; + var secondTouch = null; + if (touches.length > 1) { + secondTouch = touches[1]; + } else { + secondTouch = touches[0]; + } + + var marginBox = html.marginBox(this._map.div); + + var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - marginBox.l; + var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - marginBox.t; + + return { + x : middleX, + y : middleY + }; + + }, + + _getFingerSpacing : function(touchEvent){ + // summary: + // computes the distance between the first two fingers + // touchEvent: Event + // a touch event + // returns: float + // a distance. -1 if less that 2 fingers + // tags: + // private + var touches = touchEvent.touches; + var spacing = -1; + if (touches.length >= 2) { + var dx = (touches[1].pageX - touches[0].pageX); + var dy = (touches[1].pageY - touches[0].pageY); + spacing = Math.sqrt(dx * dx + dy * dy); + } + return spacing; + }, + + _isDoubleTap : function(touchEvent){ + // summary: + // checks whether the specified touchStart event is a double tap + // (i.e. follows closely a previous touchStart at approximately the same location) + // touchEvent: Event + // a touch event + // returns: boolean + // true if this event is considered a double tap + // tags: + // private + var isDoubleTap = false; + var touches = touchEvent.touches; + if ((this._tapCount > 0) && touches.length == 1) { + // test distance from last tap + var dx = (touches[0].pageX - this._lastTap.x); + var dy = (touches[0].pageY - this._lastTap.y); + var distance = dx * dx + dy * dy; + if (distance < this._tapThreshold) { + isDoubleTap = true; + } else { + this._tapCount = 0; + } + } + this._tapCount++; + this._lastTap.x = touches[0].pageX; + this._lastTap.y = touches[0].pageY; + setTimeout(lang.hitch(this, function(){ + this._tapCount = 0; + }), 300); + + return isDoubleTap; + }, + + _doubleTapHandler : function(touchEvent){ + // summary: + // action performed on the map when a double tap was triggered + // touchEvent: Event + // a touch event + // tags: + // private + // perform a basic 2x zoom on touch + var touches = touchEvent.touches; + var marginBox = html.marginBox(this._map.div); + var offX = touches[0].pageX - marginBox.l; + var offY = touches[0].pageY - marginBox.t; + // clicked map point before zooming + var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(offX, offY)); + // zoom increment power + this._map.setCenter(new OpenLayers.LonLat(mapPoint.lon, mapPoint.lat), this._map.getZoom() + 1); + }, + + _touchStartHandler : function(touchEvent){ + // summary: + // action performed on the map when a touch start was triggered + // touchEvent: Event + // a touch event + // tags: + // private + event.stop(touchEvent); + + // test double tap + if (this._isDoubleTap(touchEvent)) { + this._doubleTapHandler(touchEvent); + return; + } + + // compute map midpoint between fingers + var middlePoint = this._getTouchBarycenter(touchEvent); + + this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); + + // store initial finger spacing to compute zoom later + this._initialFingerSpacing = this._getFingerSpacing(touchEvent); + + // store initial map scale + this._initialScale = this._map.getScale(); + + // install touch move and up listeners (if not done by other fingers before) + if (!this._touchMoveListener) + this._touchMoveListener = connect.connect(window.global, "touchmove", this, this._touchMoveHandler); + if (!this._touchEndListener) + this._touchEndListener = connect.connect(window.global, "touchend", this, this._touchEndHandler); + + }, + + _touchEndHandler : function(touchEvent){ + // summary: + // action performed on the map when a touch end was triggered + // touchEvent: Event + // a touch event + // tags: + // private + event.stop(touchEvent); + + var touches = touchEvent.touches; + + if (touches.length == 0) { + // disconnect listeners only when all fingers are up + if (this._touchMoveListener) { + connect.disconnect(this._touchMoveListener); + this._touchMoveListener = null; + } + if (this._touchEndListener) { + connect.disconnect(this._touchEndListener); + this._touchEndListener = null; + } + } else { + // recompute touch center + var middlePoint = this._getTouchBarycenter(touchEvent); + + this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); + } + }, + + _touchMoveHandler : function(touchEvent){ + // summary: + // action performed on the map when a touch move was triggered + // touchEvent: Event + // a touch event + // tags: + // private + + // prevent browser interaction + event.stop(touchEvent); + + var middlePoint = this._getTouchBarycenter(touchEvent); + + // compute map offset + var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); + var mapOffsetLon = mapPoint.lon - this._centerTouchLocation.lon; + var mapOffsetLat = mapPoint.lat - this._centerTouchLocation.lat; + + // compute scale factor + var scaleFactor = 1; + var touches = touchEvent.touches; + if (touches.length >= 2) { + var fingerSpacing = this._getFingerSpacing(touchEvent); + scaleFactor = fingerSpacing / this._initialFingerSpacing; + // weird openlayer bug : setting several times the same scale value lead to visual zoom... + this._map.zoomToScale(this._initialScale / scaleFactor); + } + + // adjust map center on barycenter + var currentMapCenter = this._map.getCenter(); + this._map.setCenter(new OpenLayers.LonLat(currentMapCenter.lon - mapOffsetLon, currentMapCenter.lat + - mapOffsetLat)); + + } + }); +}); diff --git a/js/dojo/dojox/geo/openlayers/WidgetFeature.js b/js/dojo/dojox/geo/openlayers/WidgetFeature.js new file mode 100644 index 0000000..530fcd9 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/WidgetFeature.js @@ -0,0 +1,206 @@ +//>>built +define( + "dojox/geo/openlayers/WidgetFeature", ["dojo/_base/kernel", "dojo/_base/declare", "dojo/_base/html", "dojo/_base/lang", "dojox/geo/openlayers/Feature"], + function(dojo, declare, html, lang, Feature){ + /*===== + var Feature = dojox.geo.openlayers.Feature; + =====*/ + return declare("dojox.geo.openlayers.WidgetFeature", Feature, { + // summary: + // Wraps a Dojo widget, provide geolocalisation of the widget and interface + // to Layer class. + // description: + // This class allows to add a widget in a `dojox.geo.openlayers.Layer`. + // Parameters are passed to the constructor. These parameters describe the widget + // and provide geo-localisation of this widget. + // parameters can be: + // * _createWidget_: Function for widget creation. Must return a `dijit._Widget`. + // * _dojoType_: The class of a widget to create; + // * _dijitId_: The digitId of an existing widget. + // * _widget_: An already created widget. + // * _width_: The width of the widget. + // * _height_: The height of the widget. + // * _longitude_: The longitude, in decimal degrees where to place the widget. + // * _latitude_: The latitude, in decimal degrees where to place the widget. + // You must define a least one widget retrieval parameter and the geo-localization parameters. + _widget : null, + _bbox : null, + + constructor : function(params){ + // summary: + // Constructs a new `dojox.geo.openlayers.WidgetFeature` + // params: Object + // The parameters describing the widget. + this._params = params; + }, + + setParameters : function(params){ + // summary: + // Sets the parameters describing the widget. + // params: Object + // The parameters describing the widget. + this._params = params; + }, + + getParameters : function(){ + // summary: + // Retreives the parameters describing the widget. + // returns: Object + // The parameters describing the widget. + return this._params; + }, + + _getWidget : function(){ + // summary: + // Creates, if necessary the widget and returns it; + // tags: + // private + var params = this._params; + + if ((this._widget == null) && (params != null)) { + var w = null; + + if (typeof (params.createWidget) == "function") { + w = params.createWidget.call(this); + } else if (params.dojoType) { + dojo["require"](params.dojoType); + var c = lang.getObject(params.dojoType); + w = new c(params); + } else if (params.dijitId) { + w = dijit.byId(params.dijitId); + } else if (params.widget) { + w = params.widget; + } + + if (w != null) { + this._widget = w; + if (typeof (w.startup) == "function") + w.startup(); + var n = w.domNode; + if (n != null) + html.style(n, { + position : "absolute" + }); + } + this._widget = w; + } + return this._widget; + }, + + _getWidgetWidth : function(){ + // summary: + // gets the widget width + // tags: + // private + var p = this._params; + if (p.width) + return p.width; + var w = this._getWidget(); + if (w) + return html.style(w.domNode, "width"); + return 10; + }, + + _getWidgetHeight : function(){ + // summary: + // gets the widget height + // tags: + // private + var p = this._params; + if (p.height) + return p.height; + var w = this._getWidget(); + if (w) + return html.style(w.domNode, "height"); + return 10; + }, + + render : function(){ + // summary: + // renders the widget. + // descrption: + // Places the widget accordingly to longitude and latitude defined in parameters. + // This function is called when the center of the maps or zoom factor changes. + var layer = this.getLayer(); + + var widget = this._getWidget(); + if (widget == null) + return; + var params = this._params; + var lon = params.longitude; + var lat = params.latitude; + var from = this.getCoordinateSystem(); + var map = layer.getDojoMap(); + var p = map.transformXY(lon, lat, from); + var a = this._getLocalXY(p); + + var width = this._getWidgetWidth(); + var height = this._getWidgetHeight(); + + var x = a[0] - width / 2; + var y = a[1] - height / 2; + var dom = widget.domNode; + + var pa = layer.olLayer.div; + if (dom.parentNode != pa) { + if (dom.parentNode) + dom.parentNode.removeChild(dom); + pa.appendChild(dom); + } + this._updateWidgetPosition({ + x : x, + y : y, + width : width, + height : height + }); + }, + + _updateWidgetPosition : function(box){ + // summary: + // Places the widget with the computed x and y values + // tags: + // private + // var box = this._params; + + var w = this._widget; + var dom = w.domNode; + + html.style(dom, { + position : "absolute", + left : box.x + "px", + top : box.y + "px", + width : box.width + "px", + height : box.height + "px" + }); + + if (w.srcNodeRef) { + html.style(w.srcNodeRef, { + position : "absolute", + left : box.x + "px", + top : box.y + "px", + width : box.width + "px", + height : box.height + "px" + }); + } + + if (lang.isFunction(w.resize)) + w.resize({ + w : box.width, + h : box.height + }); + }, + + remove : function(){ + // summary: + // removes this feature. + // description: + // Remove this feature by disconnecting the widget from the dom. + var w = this.getWidget(); + if (!w) + return; + var dom = w.domNode; + if (dom.parentNode) + dom.parentNode.removeChild(dom); + } + }); + }); diff --git a/js/dojo/dojox/geo/openlayers/widget/Map.js b/js/dojo/dojox/geo/openlayers/widget/Map.js new file mode 100644 index 0000000..da29758 --- /dev/null +++ b/js/dojo/dojox/geo/openlayers/widget/Map.js @@ -0,0 +1,161 @@ +//>>built +define("dojox/geo/openlayers/widget/Map", ["dojo/_base/kernel", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/html", + "dojo/query", + "dijit/_Widget", + "dojox/geo/openlayers/Map", + "dojox/geo/openlayers/Layer", + "dojox/geo/openlayers/GfxLayer"], function(dojo, declare, array, html, query, Widget, Map, Layer, GfxLayer){ + /*===== + var Widget = dijit.Widget; + =====*/ + return declare("dojox.geo.openlayers.widget.Map", Widget, { + // summary: + // A widget version of the `dojox.geo.openlayers.Map` component. + // description: + // The `dojox.geo.openlayers.widget.Map` widget is the widget + // version of the `dojox.geo.openlayers.Map` component. + // With this widget, user can specify some attributes in the markup suach as + // + // * `baseLayerType`: The type of the base layer. Permitted values are + // * `initialLocation`: The initial location as for the dojox.geo.openlayers.Map.fitTo method + // * `touchHandler`: Tells if we attach touch handler or not. + // + // example: + // + // | <div id="map" dojoType="dojox.geo.openlayers.widget.Map" baseLayerType="Google" initialLocation="{ + // | position : [7.154126, 43.651748], + // | extent : 0.2 }" + // | style="background-color: #b5d0d0; width: 100%; height: 100%;"> + // + + // summay: + // Base layer type as defined in `dojox.geo.openlayer.BaseLayerType + // description: + // baseLayerType can be either + // * `OSM` + // * `WMS` + // * `Google` + // * `VirtualEarth` + // * `Yahoo` + // * `ArcGIS` + // baseLayerType : String + // Base layer type property. + baseLayerType : dojox.geo.openlayers.BaseLayerType.OSM, + + // summary: + // The part of the map shown at startup time. + // description: + // initial location is the string description of the location shown at + // startup time. Format is the same as for the `dojox.geo.openlayers.widget.Map.fitTo` + // method. + // | { + // | bounds : [ulx, uly, lrx, lry] + // | } + // The map is fit on the specified bounds expressed as decimal degrees latitude and longitude. + // The bounds are defined with their upper left and lower right corners coordinates. + // + // | { + // | position : [longitude, latitude], + // | extent : degrees + // | } + // The map is fit on the specified position showing the extent <extent> around + // the specified center position. + initialLocation : null, + + // summary: + // Tells if the touch handler should be attached to the map or not. + // description: + // Tells if the touch handler should be attached to the map or not. + // Touch handler handles touch events so that the widget can be used + // on mobile applications. + touchHandler : false, + + // summary: + // The underlying `dojox.geo.openlayers.Map` object. + // This is s readonly member. + map : null, + + startup : function(){ + // summary: + // Processing after the DOM fragment is added to the document + this.inherited(arguments); + this.map.initialFit({ + initialLocation : this.initialLocation + }); + }, + + buildRendering : function(){ + // summary: + // Construct the UI for this widget, creates the real dojox.geo.openlayers.Map object. + // tags: + // protected + this.inherited(arguments); + var div = this.domNode; + var map = new Map(div, { + baseLayerType : this.baseLayerType, + touchHandler : this.touchHandler + }); + this.map = map; + + this._makeLayers(); + }, + + _makeLayers : function(){ + // summary: + // Creates layers defined as markup. + // tags: + // private + var n = this.domNode; + var layers = /* ?? query. */query("> .layer", n); + array.forEach(layers, function(l){ + var type = l.getAttribute("type"); + var name = l.getAttribute("name"); + var cls = "dojox.geo.openlayers." + type; + var p = dojo.getObject(cls); + if (p) { + var layer = new p(name, {}); + if (layer) + this.map.addLayer(layer); + } + }, this); + }, + + resize : function(b){ + // summary: + // Resize the widget. + // description: + // Resize the domNode and the widget to the dimensions of a box of the following form: + // `{ l: 50, t: 200, w: 300: h: 150 }` + // b: undefined | Box | width, height + // If passed, denotes the new size of the widget. + // Can be either nothing (widget adapts to the div), + // a box, or a width and a height. + + var olm = this.map.getOLMap(); + + var box; + switch (arguments.length) { + case 0: + // case 0, do not resize the div, just the surface + break; + case 1: + // argument, override node box + box = dojo.mixin({}, b); + dojo.marginBox(olm.div, box); + break; + case 2: + // two argument, width, height + box = { + w : arguments[0], + h : arguments[1] + }; + dojo.marginBox(olm.div, box); + break; + } + olm.updateSize(); + } + }); +}); |
